package com.uinnova.product.eam.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.binary.core.exception.BinaryException;
import com.binary.core.exception.MessageException;
import com.binary.core.i18n.LanguageResolver;
import com.binary.core.io.Compression;
import com.binary.core.lang.StringUtils;
import com.binary.core.util.BinaryUtils;
import com.binary.framework.exception.ServiceException;
import com.binary.jdbc.Page;
import com.binary.json.JSON;
import com.google.common.collect.Lists;
import com.uinnova.product.eam.base.util.EamUtil;
import com.uinnova.product.eam.base.util.ExcelUtil;
import com.uinnova.product.eam.model.CiQueryCdtExtend;
import com.uinnova.product.eam.model.bm.SavePrivateBatchCIContext;
import com.uinnova.product.eam.model.dto.CiCodeDto;
import com.uinnova.product.eam.model.vo.*;
import com.uinnova.product.eam.service.BmDiagramSvc;
import com.uinnova.product.eam.service.IEamCIClassApiSvc;
import com.uinnova.product.eam.service.es.IamsCIPrivateNonComplianceDao;
import com.uinnova.product.eam.service.es.IamsESCIDesignSvc;
import com.uinnova.product.eam.service.es.IamsESCIPrivateSvc;
import com.uinnova.product.eam.service.es.IamsESCmdbCommPrivateSvc;
import com.uinnova.product.vmdb.comm.bean.CIState;
import com.uinnova.product.vmdb.comm.model.ci.CCcCi;
import com.uinnova.product.vmdb.comm.model.ci.CcCi;
import com.uinnova.product.vmdb.comm.model.ci.CcCiAttrDef;
import com.uinnova.product.vmdb.comm.model.ci.CcCiClass;
import com.uinnova.product.vmdb.comm.util.CiExcelUtil;
import com.uinnova.product.vmdb.comm.util.CommUtil;
import com.uinnova.product.vmdb.provider.ci.bean.*;
import com.uinnova.product.vmdb.provider.search.bean.CcCiSearchPage;
import com.uino.bean.cmdb.base.*;
import com.uino.bean.cmdb.business.*;
import com.uino.bean.cmdb.enums.AttrNameKeyEnum;
import com.uino.bean.cmdb.query.ESAttrAggBean;
import com.uino.bean.cmdb.query.ESCISearchBean;
import com.uino.bean.permission.base.SysRole;
import com.uino.bean.permission.base.SysRoleDataModuleRlt;
import com.uino.bean.permission.base.SysUser;
import com.uino.bean.permission.business.CIExportLabel;
import com.uino.bean.permission.query.CSysRoleDataModuleRlt;
import com.uino.bean.permission.query.CSysUser;
import com.uino.bean.sys.base.ESCIOperateLog;
import com.uino.dao.BaseConst;
import com.uino.dao.cmdb.*;
import com.uino.dao.util.ESUtil;
import com.uino.service.cmdb.microservice.ICISvc;
import com.uino.service.cmdb.microservice.impl.CIClassSvc;
import com.uino.service.permission.microservice.IUserSvc;
import com.uino.service.permission.microservice.impl.RoleSvc;
import com.uino.service.sys.microservice.ICIOperateLogSvc;
import com.uino.service.sys.microservice.IDictionarySvc;
import com.uino.service.sys.microservice.IResourceSvc;
import com.uino.service.util.FileUtil;
import com.uino.util.excel.EasyExcelUtil;
import com.uino.util.rsm.RsmUtils;
import com.uino.util.sys.CheckAttrUtil;
import com.uino.util.sys.SysUtil;
import com.uino.util.sys.ValidDtoUtil;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.*;
import java.math.BigDecimal;
import java.net.URLDecoder;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author zoumengjie
 */
@Service
public class IamsCIPrivateSvc implements ICISvc {

    private static final Logger log = LoggerFactory.getLogger(IamsCIPrivateSvc.class);

    @Resource
    IUserSvc userSvc;

    @Resource
    IamsESCIPrivateSvc esCiSvc;

    @Resource
    IamsESCIDesignSvc esDesginCiSvc;
    @Resource
    ESImageSvc esImageSvc;

    @Resource
    ESCIClassSvc esClsSvc;

    @Resource
    CIClassSvc clsSvc;

    @Resource
    IamsCIRltPrivateSvc ciRltSvc;

    @Resource
    @Lazy
    IamsESCmdbCommPrivateSvc commSvc;

    @Resource
    IResourceSvc resourceSvc;

    @Resource
    ICIOperateLogSvc cIOperateLogSvc;

    @Resource
    IDictionarySvc dictSvc;

    @Autowired
    private IEamCIClassApiSvc classApiSvc;

    @Autowired
    private ESCIAttrTransConfigSvc attrTransConfigSvc;

    @Autowired
    private ESCIHistorySvc ciHistorySvc;

    @Resource
    private IEamCIClassApiSvc ciClassApiSvc;

    @Autowired
    private RoleSvc roleSvc;

    @Autowired
    private IamsCIPrivateNonComplianceDao iamsCIPrivateNonComplianceDao;

    @Autowired
    private RsmUtils rsmUtils;

    @Autowired
    private BmDiagramSvc bmDiagramSvc;

    @Value("${is.show.3d.attribute:false}")
    Boolean isShow3dAttribute;

    @Value("${http.resource.space}")
    String urlPath;

    @Value("${local.resource.space}")
    String localPath;

    private static final String OWNER_CODE="attrs.所属用户";

    private static final String OWNER_CODE_KEYWORD = "ownerCode.keyword";

    private static final String DEFAULT_COPY_POSTFIX = "_copy";
    private static final String SHEET_COPY_POSTFIX = "_副本";


    @Override
    public Long saveOrUpdate(CcCiInfo ciInfo) {
        return this.saveOrUpdateMethod(ciInfo, null);
    }

    @Override
    public Long saveOrUpdate(CcCiInfo ciInfo, SaveType saveType) {
        return this.saveOrUpdateMethod(ciInfo, saveType);
    }

    public Long saveOrUpdateMethod (CcCiInfo ciInfo, SaveType saveType) {
        CcCiClass ccCiClass = ciInfo.getCiClass();
        String classCode = null;
        if(!BinaryUtils.isEmpty(ccCiClass)){
             classCode =ccCiClass.getClassCode();
        }
        Long id = ciInfo.getCi().getClassId();
        if(BinaryUtils.isEmpty(classCode) && BinaryUtils.isEmpty(id)){
            throw new ServiceException("classId和classCode不能同时为空");
        }
        ESCIClassInfo classInfo = null;
        if(!BinaryUtils.isEmpty(id)){
            classInfo = ciClassApiSvc.queryClassById(id);
        }
        if(BinaryUtils.isEmpty(classInfo) && !BinaryUtils.isEmpty(classCode)){
            //根据classId没查到，再根据classCode查一遍；
            CcCiClassInfo ciClass = ciClassApiSvc.getCIClassByCodes(ciInfo.getCiClass().getClassCode());
            classInfo = EamUtil.copy(ciClass.getCiClass(), ESCIClassInfo.class);
        }
        if(BinaryUtils.isEmpty(classInfo)){
            throw MessageException.i18n("BS_MNAME_CLASS_NOT_EXSIT");
        }
        final CcCi ci = ciInfo.getCi();
        final Map<String, String> attrs = ciInfo.getAttrs();
        Assert.notNull(ci, "X_PARAM_NOT_NULL${name:ci}");
        Assert.notNull(attrs, "X_PARAM_NOT_NULL${name:ci}");
        ci.setState(CIState.CREATE_COMPLETE.val());
        boolean isCreate = BinaryUtils.isEmpty(ci.getId());
        final Long classId = classInfo.getId();
        SysUser user = SysUtil.getCurrentUserInfo();
        final String modifier = user.getLoginCode();
        final Long domainId = user.getDomainId();
        final String ownerCode = ci.getOwnerCode();
        String ciCode = ciInfo.getCi().getCiCode();
        Assert.notNull(domainId, "X_PARAM_NOT_NULL${name:domainId}");
        Assert.notNull(ownerCode, "X_PARAM_NOT_NULL${name:ownerCode}");
        String classStdCode = classInfo.getClassStdCode();
        // 校验CI属性-当前及父类属性
        List<CcCiAttrDef> attrDefs = esClsSvc.getAllDefsByClassId(domainId, classId);
        //业务主键key
        List<String> ciPKAttrDefNames = CommUtil.getCiPKAttrDefNames(attrDefs);
        CIState state = commSvc.generateStateAndValidAttrs(attrDefs, attrs, ciPKAttrDefNames);
        ci.setState(state.val());
        getEnCodeNumByDef(classInfo.getClassCode(), attrDefs, attrs);
        Map<String, String> stdMap = toStdMap(attrs);
        Integer hashCode = 0;
        List<String> ciPrimaryKeys = Collections.emptyList();
        if (ci.getState() > CIState.CREATE_INIT.val()) {
            hashCode = CommUtil.getCiMajorHashCode(stdMap, ciPKAttrDefNames, classStdCode, ownerCode);
            ciPrimaryKeys = CommUtil.getCiPrimaryKeys(classStdCode, stdMap, ciPKAttrDefNames);
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            boolQuery.must(QueryBuilders.termQuery("hashCode", hashCode));
            boolQuery.must(QueryBuilders.termQuery("classId", classId));
            boolQuery.must(QueryBuilders.termQuery(OWNER_CODE_KEYWORD, ownerCode));
            List<ESCIInfo> liveCis = esCiSvc.getListByQuery(boolQuery);
            for (ESCIInfo eachCi : liveCis) {
                if (compareCiPrimaryKeys(ciPrimaryKeys, JSON.toList(eachCi.getCiPrimaryKey(), String.class))) {
                    // 同分类下ciCode或id相同，更新，否则删除
                    // 有id时优先按id更新，兼容仅有ciCode时更新的情况
                    boolean idEqual = ci.getId() != null && ci.getId().longValue() == eachCi.getId().longValue();
                    boolean codeEqual = ciCode != null && ciCode.equals(eachCi.getCiCode());
                    boolean isUpdate = (codeEqual || idEqual);
                    if (isUpdate) {
                        if (ci.getId() != null) {
                            Assert.isTrue(idEqual, "BS_CI_NO_EXIST");
                        }
                        if (ciCode != null) {
                            Assert.isTrue(codeEqual, "BS_CI_NO_EXIST");
                        }
                        ci.setId(eachCi.getId());
                        ci.setCiCode(eachCi.getCiCode());
                        isCreate = false;
                    } else {
                        if (!BinaryUtils.isEmpty(saveType) || SaveType.WRITE == saveType) {
                            throw new ServiceException("该对象已存在于私有库");
                        } else {
                            return eachCi.getId();
                        }
                    }
                }
            }
        }
        if (isCreate && !BinaryUtils.isEmpty(ciCode)) {
            BoolQueryBuilder ciCodeQuery = QueryBuilders.boolQuery();
            ciCodeQuery.must(QueryBuilders.termQuery("ciCode.keyword", ciCode));
            ciCodeQuery.must(QueryBuilders.termQuery(OWNER_CODE_KEYWORD, ownerCode));
            List<ESCIInfo> result = esCiSvc.getListByQuery(ciCodeQuery);
            if (!BinaryUtils.isEmpty(result)) {
                ci.setId(result.get(0).getId());
                isCreate = false;
            }
        }
        // 属性过滤，只保存定义过的属性
        Iterator<String> itAttr = stdMap.keySet().iterator();
        List<String> defNames = attrDefs.stream().map(CcCiAttrDef::getProStdName).collect(Collectors.toList());
        while (itAttr.hasNext()) {
            String attrName = itAttr.next();
            // 属性不包含在定义的属性，并且不是下划线开头结尾的字段，去掉
            if (!defNames.contains(attrName.trim()) && !attrName.matches("_([\\d\\D]*?)_")) {
                itAttr.remove();
            }
        }
        // 组装CI
        if (isCreate) {
            long uuid = ESUtil.getUUID();
            ci.setId(uuid);
            ci.setCiCode(BinaryUtils.isEmpty(ciCode) ? String.valueOf(uuid) : ciCode);
            ci.setCreateTime(ESUtil.getNumberDateTime());
            ci.setCiVersion(String.valueOf(0));
            ci.setLocalVersion(1L);
            ci.setPublicVersion(0L);
            // 当前CI创建判断是否为设计库关联数据
            if (!BinaryUtils.isEmpty(ciCode)) {
                // 查询设计库对应的 CI 数据
                CCcCi cdt = new CCcCi();
                cdt.setCiCodeEqual(ciCode);
                List<CcCiInfo> ccCiInfos = esDesginCiSvc.queryCiInfoList(cdt, null, false, false);
                if(!BinaryUtils.isEmpty(ccCiInfos)){
                    Map<String, String> oldAttrs = ccCiInfos.get(0).getAttrs();
                    if (!CheckAttrUtil.checkAttrMapEqual(stdMap, oldAttrs)) {
                        ci.setCiVersion(String.valueOf(Long.parseLong(ccCiInfos.get(0).getCi().getCiVersion()) + 1L));
                        ci.setPublicVersion(ccCiInfos.get(0).getCi().getPublicVersion() + 1L);
                        ci.setLocalVersion(1L);
                    } else {
                        ci.setPublicVersion(ccCiInfos.get(0).getCi().getPublicVersion());
                        ci.setLocalVersion(0L);
                    }
                }
            }
        } else {
            CcCiInfo dbCIInfo = esCiSvc.getCiInfoById(ci.getId());
            Assert.notNull(dbCIInfo, "BS_CI_NO_EXIST");
            Map<String, String> oldAttrs = dbCIInfo.getAttrs();
            ci.setCiCode(dbCIInfo.getCi().getCiCode());
            ci.setCreateTime(dbCIInfo.getCi().getCreateTime());
            ci.setCreator(dbCIInfo.getCi().getCreator());
            ci.setCiVersion(dbCIInfo.getCi().getCiVersion());
            ci.setLocalVersion(dbCIInfo.getCi().getLocalVersion());
            ci.setPublicVersion(selectVersion(ci, dbCIInfo.getCi()));
            if (!CheckAttrUtil.checkAttrMapEqual(stdMap, oldAttrs)) {
                ci.setCiVersion(String.valueOf(Long.parseLong(dbCIInfo.getCi().getCiVersion()) + 1L));
                ci.setLocalVersion(dbCIInfo.getCi().getLocalVersion() + 1L);
            }
        }
        ciInfo.setAttrs(stdMap);
        ci.setClassId(classId);
        ci.setOwnerCode(ownerCode);
        ci.setDataStatus(1);
        ci.setHashCode(hashCode);
        ci.setDiagramId(null);
        ci.setModifier(modifier);
        if (ci.getState() == CIState.CREATE_INIT.val() && BinaryUtils.isEmpty(ciPrimaryKeys)) {
            ciPrimaryKeys = Collections.singletonList(ci.getCiCode());
        }
        ci.setCiPrimaryKey(JSON.toString(ciPrimaryKeys));
        return esCiSvc.saveOrUpdateCI(ciInfo);
    }

    public <T> void getEnCodeNumByDef(String classCode, List<CcCiAttrDef> defs, Map<String, T> attrs) {
        List<CcCiAttrDef> pros = defs.stream().filter(each -> each.getProType() == 150).collect(Collectors.toList());
        CiCodeDto ciCodeDto = new CiCodeDto();
        ciCodeDto.setClassCode(classCode);
        for (CcCiAttrDef pro : pros) {
            if(pro.getDefVal().equals(attrs.get(pro.getProName())) || BinaryUtils.isEmpty(attrs.get(pro.getProName()))){
                ciCodeDto.setDefVal(pro.getDefVal());
                ciCodeDto.setProName(pro.getProName());
                ciCodeDto.setStartNum(pro.getEnumValues());
                String num = classApiSvc.getEnCodeNum(ciCodeDto);
                attrs.put(pro.getProName(), (T)num);
            }
        }
    }

    @Override
    public Long saveOrUpdateExtra(CcCiInfo ciInfo) {
        final CcCi ci = ciInfo.getCi();
        final Map<String, String> attrs = ciInfo.getAttrs();
        Assert.notNull(ci, "X_PARAM_NOT_NULL${name:ci}");
        Assert.notNull(attrs, "X_PARAM_NOT_NULL${name:ci}");
        ci.setState(CIState.CREATE_COMPLETE.val());
        boolean isCreate = BinaryUtils.isEmpty(ci.getId());
        final Long classId = ci.getClassId();
        SysUser user = SysUtil.getCurrentUserInfo();
        final String modifier = user.getLoginCode();
        final Long domainId = user.getDomainId();
        final String ownerCode = ci.getOwnerCode();
        String ciCode = ciInfo.getCi().getCiCode();
        Assert.notNull(classId, "X_PARAM_NOT_NULL${name:classId}");
        Assert.notNull(domainId, "X_PARAM_NOT_NULL${name:domainId}");
        Assert.notNull(ownerCode, "X_PARAM_NOT_NULL${name:ownerCode}");

        ESCIClassInfo ciClass = esClsSvc.getById(classId);
        if (ciClass == null) {// 分类是否存在
            throw MessageException.i18n("BS_MNAME_CLASS_NOT_EXSIT");
        }
        String classStdCode = ciClass.getClassStdCode();
        List<CcCiAttrDef> attrDefs = esClsSvc.getAllDefsByClassId(domainId, classId); // 校验CI属性-当前及父类属性
        //业务主键key
        List<String> ciPKAttrDefNames = CommUtil.getCiPKAttrDefNames(attrDefs);

        CIState state = commSvc.generateStateAndValidAttrs(attrDefs, attrs, ciPKAttrDefNames);
        ci.setState(state.val());
        getEnCodeNumByDef(ciClass.getClassCode(), attrDefs, attrs);
        Map<String, String> stdMap = toStdMap(attrs);
        Integer hashCode = 0;
        List<String> ciPrimaryKeys = Collections.emptyList();
        if (ci.getState() > CIState.CREATE_INIT.val()) {
            hashCode = CommUtil.getCiMajorHashCode(stdMap, ciPKAttrDefNames, classStdCode, ownerCode);
            ciPrimaryKeys = CommUtil.getCiPrimaryKeys(classStdCode, stdMap, ciPKAttrDefNames);
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            boolQuery.must(QueryBuilders.termQuery("hashCode", hashCode));
            boolQuery.must(QueryBuilders.termQuery("classId", classId));
            boolQuery.must(QueryBuilders.termQuery(OWNER_CODE_KEYWORD, ownerCode));
            List<ESCIInfo> liveCis = esCiSvc.getListByQuery(boolQuery);
            for (ESCIInfo eachCi : liveCis) {
                if (compareCiPrimaryKeys(ciPrimaryKeys, JSON.toList(eachCi.getCiPrimaryKey(), String.class))) {
                    // 同分类下ciCode或id相同，更新，否则删除
                    // 有id时优先按id更新，兼容仅有ciCode时更新的情况
                    boolean idEqual = ci.getId() != null && ci.getId().longValue() == eachCi.getId().longValue();
                    boolean codeEqual = ciCode != null && ciCode.equals(eachCi.getCiCode());
                    boolean isUpdate = (codeEqual || idEqual);
                    if (isUpdate) {
                        if (ci.getId() != null) {
                            Assert.isTrue(idEqual, "BS_CI_NO_EXIST");
                        }
                        if (ciCode != null) {
                            Assert.isTrue(codeEqual, "BS_CI_NO_EXIST");
                        }
                        ci.setId(eachCi.getId());
                        ci.setCiCode(eachCi.getCiCode());
                        isCreate = false;
                    } else {
                        throw MessageException.i18n("BS_MNAME_RECORD_CONTAINS");
                    }
                }
            }
        }
        if (isCreate && !BinaryUtils.isEmpty(ciCode)) {
            BoolQueryBuilder ciCodeQuery = QueryBuilders.boolQuery();
            ciCodeQuery.must(QueryBuilders.termQuery("ciCode.keyword", ciCode));
            ciCodeQuery.must(QueryBuilders.termQuery(OWNER_CODE_KEYWORD, ownerCode));
            List<ESCIInfo> result = esCiSvc.getListByQuery(ciCodeQuery);
            if (!BinaryUtils.isEmpty(result)) {
                ci.setId(result.get(0).getId());
                isCreate = false;
            }
        }
        // 属性过滤，只保存定义过的属性
        Iterator<String> itAttr = stdMap.keySet().iterator();
        List<String> defNames = attrDefs.stream().map(CcCiAttrDef::getProStdName).collect(Collectors.toList());
        while (itAttr.hasNext()) {
            String attrName = itAttr.next();
            // 属性不包含在定义的属性，并且不是下划线开头结尾的字段，去掉
            if (!defNames.contains(attrName) && !attrName.matches("_([\\d\\D]*?)_")) {
                itAttr.remove();
            }
        }
        // 组装CI
        if (isCreate) {
            long uuid = ESUtil.getUUID();
            ci.setId(uuid);
            ci.setCiCode(BinaryUtils.isEmpty(ciCode) ? String.valueOf(uuid) : ciCode);
            ci.setCreateTime(ESUtil.getNumberDateTime());
            ci.setCiVersion(String.valueOf(0));
            ci.setLocalVersion(1L);
            if (BinaryUtils.isEmpty(ci.getPublicVersion())) {
                ci.setPublicVersion(0L);
            }
        } else {
            CcCiInfo dbCIInfo = esCiSvc.getCiInfoById(ci.getId());
            Assert.notNull(dbCIInfo, "BS_CI_NO_EXIST");
            Map<String, String> oldAttrs = dbCIInfo.getAttrs();
            ci.setCiCode(dbCIInfo.getCi().getCiCode());
            ci.setCreateTime(dbCIInfo.getCi().getCreateTime());
            ci.setCreator(dbCIInfo.getCi().getCreator());
            ci.setCiVersion(dbCIInfo.getCi().getCiVersion());
            ci.setLocalVersion(dbCIInfo.getCi().getLocalVersion());
            ci.setPublicVersion(selectVersion(ci, dbCIInfo.getCi()));
            if (!CheckAttrUtil.checkAttrMapEqual(stdMap, oldAttrs)) {
                ci.setCiVersion(String.valueOf(Long.parseLong(dbCIInfo.getCi().getCiVersion()) + 1L));
                ci.setLocalVersion(dbCIInfo.getCi().getLocalVersion() + 1L);
            }
        }
        ciInfo.setAttrs(stdMap);
        ci.setClassId(classId);
        ci.setOwnerCode(ownerCode);
        ci.setDataStatus(1);
        ci.setHashCode(hashCode);
        ci.setDiagramId(null);
        ci.setModifier(modifier);
        if (ci.getState() == CIState.CREATE_INIT.val() && BinaryUtils.isEmpty(ciPrimaryKeys)) {
            ciPrimaryKeys = Collections.singletonList(ci.getCiCode());
        }
        ci.setCiPrimaryKey(JSON.toString(ciPrimaryKeys));
        return esCiSvc.saveOrUpdateCI(ciInfo);
    }

    private long selectVersion(CcCi createCi, CcCi historyCi) {
        long version = createCi.getPublicVersion();
        boolean compare = version < historyCi.getPublicVersion();
        if (compare) {
            version = historyCi.getPublicVersion();
        }
        return version;
    }

    @Override
    public Map<String, SavePrivateBatchCIContext> saveOrUpdateBatchCI(List<ESCIInfo> ciList, List<Long> classIds, String ownerCode, String loginCode) {
        Map<Long, ESCIClassInfo> classMap = esClsSvc.selectMapByQuery(1, classIds.size(), QueryBuilders.termsQuery("id", classIds));
        Map<String, SavePrivateBatchCIContext> paramsContext = new HashMap<>();
        Map<Long, Map<Integer, List<SavePrivateBatchCIContext>>> hashCodeMap = new HashMap<>();
        for (ESCIInfo item : ciList) {
            Assert.notNull(item, "X_PARAM_NOT_NULL${name:ci}");
            Long classId = item.getClassId();
            Assert.notNull(classId, "X_PARAM_NOT_NULL${name:classId}");
            String ciCode = item.getCiCode();
            Assert.notNull(ciCode, "X_PARAM_NOT_NULL${name:ciCode}");
            SavePrivateBatchCIContext context = paramsContext.computeIfAbsent(item.getCiCode(), k -> new SavePrivateBatchCIContext(ciCode, classId, item));
            Map<String, Object> attrs = item.getAttrs();
            context.setId(item.getId());
            final boolean isAdd = BinaryUtils.isEmpty(context.getId());
            String ciOwnerCode = BinaryUtils.isEmpty(item.getOwnerCode()) ? ownerCode : item.getOwnerCode();
            context.setOwnerCode(ciOwnerCode);
            context.setAdd(isAdd);
            ESCIClassInfo ciClass = classMap.get(classId);
            if (ciClass == null) {
                throw MessageException.i18n("BS_MNAME_CLASS_NOT_EXSIT");
            }
            context.setClassStdCode(ciClass.getClassStdCode());
            context.setClassName(ciClass.getClassName());
            // 校验CI属性-当前及父类属性
            context.setAttrsObj(attrs);
            context.setAttrsStr(coverToAttrs(attrs));
            //业务主键key
            List<String> ciPKAttrDefNames = CommUtil.getCiPKAttrDefNames(ciClass.getCcAttrDefs());
            CIState state = commSvc.generateStateAndValidAttrs(ciClass.getCcAttrDefs(), context.getAttrsStr(), ciPKAttrDefNames);
            item.setState(state.val());
            context.setState(state.val());
            Map<String, String> stdMap = CheckAttrUtil.toStdMap(context.getAttrsStr());
            context.setStdMap(stdMap);
            context.setAttrDefs(ciClass.getCcAttrDefs());
            Integer hashCode = 0;
            List<String> ciPrimaryKeys = Collections.emptyList();
            if (state.val() > 0) {
                hashCode = CommUtil.getCiMajorHashCode(context.getAttrsStr(), ciPKAttrDefNames, context.getClassStdCode(), ownerCode);
                ciPrimaryKeys = CommUtil.getCiPrimaryKeys(context.getClassStdCode(), context.getAttrsStr(), ciPKAttrDefNames);
                Map<Integer, List<SavePrivateBatchCIContext>> map = hashCodeMap.computeIfAbsent(classId, k -> new HashMap<>());
                List<SavePrivateBatchCIContext> repeatCtxList = map.computeIfAbsent(hashCode, k -> new ArrayList<>());
                for (SavePrivateBatchCIContext repeatCtx : repeatCtxList) {
                    if (compareCiPrimaryKeys(repeatCtx.getPrimaryKeys(), ciPrimaryKeys) && repeatCtx.getOwnerCode().equals(context.getOwnerCode())) {
                        if(ciOwnerCode.equals(loginCode)){
                            throw new ServiceException("该对象已存在于私有库,请从私有库拖入该对象！");
                        } else {
                            throw new ServiceException("该对象已存在于协作共享库,请从协作共享库拖入该对象！");
                        }
                    }
                }
                repeatCtxList.add(context);
            }
            context.setPrimaryKeys(ciPrimaryKeys);
            context.setHashCode(hashCode);
        }
        List<SavePrivateBatchCIContext> contexts = paramsContext.values().stream().filter(each -> each.getState() > 0).collect(Collectors.toList());
        if (!BinaryUtils.isEmpty(contexts)) {
            Map<Integer, List<SavePrivateBatchCIContext>> hashCodesMap = contexts.stream().collect(Collectors.groupingBy(SavePrivateBatchCIContext::getHashCode));
            BoolQueryBuilder hashCodeQuery = QueryBuilders.boolQuery();
            hashCodeQuery.must(QueryBuilders.termsQuery("hashCode", hashCodesMap.keySet()));
            hashCodeQuery.must(QueryBuilders.termQuery(OWNER_CODE_KEYWORD, ownerCode));
            List<CcCiInfo> liveCis = esCiSvc.getCIInfoPageByQuery(1, hashCodesMap.size() * 100, hashCodeQuery, false).getData();
            for (CcCiInfo cInfo : liveCis) {
                List<SavePrivateBatchCIContext> contextList = hashCodesMap.get(cInfo.getCi().getHashCode());
                if (BinaryUtils.isEmpty(contextList)) {
                    continue;
                }
                for (SavePrivateBatchCIContext repeatCtx : contextList) {
                    if (compareCiPrimaryKeys(repeatCtx.getPrimaryKeys(), JSON.toList(cInfo.getCi().getCiPrimaryKey(), String.class))) {
                        boolean idEqual = repeatCtx.getId() != null && repeatCtx.getId().longValue() == cInfo.getCi().getId().longValue();
                        boolean classIdEqual = repeatCtx.getClassId().longValue() == cInfo.getCi().getClassId().longValue();
                        boolean isUpdate = idEqual || classIdEqual;
                        if (isUpdate) {
//                            if (repeatCtx.getId() != null) {
//                                Assert.isTrue(idEqual, "BS_CI_NO_EXIST");
//                            }
                            repeatCtx.setId(cInfo.getCi().getId());
                            repeatCtx.setAdd(false);
                        } else {
                            if(ownerCode.equals(loginCode)){
                                throw new ServiceException("该对象已存在于私有库,请从私有库拖入该对象！");
                            } else {
                                throw new ServiceException("该对象已存在于协作共享库,请从协作共享库拖入该对象！");
                            }
                        }
                    }
                }
            }
        }

        Map<String, ESCIInfo> desginCodeAnfDataMap = new HashMap<>();
        List<SavePrivateBatchCIContext> addContext = paramsContext.values().stream().filter(SaveBatchCIContext::isAdd).collect(Collectors.toList());
        if (!BinaryUtils.isEmpty(addContext)) {
            List<String> ciCodes = addContext.stream().map(SavePrivateBatchCIContext::getCiCode).collect(Collectors.toList());
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            boolQuery.must(QueryBuilders.termsQuery("ciCode.keyword", ciCodes));
            List<ESCIInfo> desginCIInfo = esDesginCiSvc.getListByQueryScroll(boolQuery);
            for (ESCIInfo esciInfo:desginCIInfo) {
                desginCodeAnfDataMap.put(esciInfo.getCiCode(), esciInfo);
            }
            boolQuery.must(QueryBuilders.termQuery(OWNER_CODE_KEYWORD, ownerCode));
            List<ESCIInfo> ciCodeQuery = esCiSvc.getListByQueryScroll(boolQuery);
            if (!BinaryUtils.isEmpty(ciCodeQuery)) {
                for (ESCIInfo dbCi : ciCodeQuery) {
                    SavePrivateBatchCIContext saveBatchCIContext = paramsContext.get(dbCi.getCiCode());
                    saveBatchCIContext.setId(dbCi.getId());
                    saveBatchCIContext.setAdd(false);
                }
            }
        }
        List<SavePrivateBatchCIContext> updateContext = paramsContext.values().stream().filter(ctx -> !ctx.isAdd()).collect(Collectors.toList());
        Map<Long, ESCIInfo> ciDbMap = new HashMap<>();
        if (!BinaryUtils.isEmpty(updateContext)) {
            List<Long> ciIds = updateContext.stream().map(SavePrivateBatchCIContext::getId).collect(Collectors.toList());
            List<ESCIInfo> ciCodeQuery = esCiSvc.getListByQueryScroll(QueryBuilders.termsQuery("id", ciIds));
            if (!BinaryUtils.isEmpty(ciCodeQuery)) {
                for (ESCIInfo esciInfo : ciCodeQuery) {
                    ciDbMap.put(esciInfo.getId(), esciInfo);
                }
            }
        }
        List<String> removeList = new ArrayList<>();
        for (SavePrivateBatchCIContext ctx : paramsContext.values()) {
            // 属性过滤，只保存定义过的属性
            Iterator<String> itAttr = ctx.getStdMap().keySet().iterator();
            List<String> defNames = ctx.getAttrDefs().stream().map(CcCiAttrDef::getProStdName).collect(Collectors.toList());
            while (itAttr.hasNext()) {
                String attrName = itAttr.next();
                // 属性不包含在定义的属性，并且不是下划线开头结尾的字段，去掉
                if (!defNames.contains(attrName) && !attrName.matches("_([\\d\\D]*?)_")) {
                    itAttr.remove();
                }
            }
            long publicVersion = ctx.getEsCi().getPublicVersion();
            // 组装CI
            if (ctx.isAdd()) {
                long uuid = ESUtil.getUUID();
                ctx.getEsCi().setId(uuid);
                ctx.getEsCi().setCiCode(ctx.getCiCode());
                ctx.getEsCi().setCreator(loginCode);
                ctx.getEsCi().setCreateTime(ESUtil.getNumberDateTime());
                ctx.getEsCi().setCiVersion(String.valueOf(0));
                ctx.getEsCi().setLocalVersion(1L);
                ctx.getEsCi().setPublicVersion(0L);
                if (!BinaryUtils.isEmpty(ctx.getCiCode())) {
                    ESCIInfo esciInfo = desginCodeAnfDataMap.get(ctx.getCiCode());
                    if(!BinaryUtils.isEmpty(esciInfo)){
                        ctx.getEsCi().setPublicVersion(desginCodeAnfDataMap.get(ctx.getCiCode()).getPublicVersion());
                        ctx.getEsCi().setLocalVersion(0L);
                    }
                }
            } else {
                ESCIInfo dbCIInfo = ciDbMap.get(ctx.getId());
                if(dbCIInfo == null){
                    removeList.add(ctx.getCiCode());
                    continue;
                }
                ctx.getEsCi().setId(dbCIInfo.getId());
                ctx.getEsCi().setCiCode(dbCIInfo.getCiCode());
                ctx.getEsCi().setCreateTime(dbCIInfo.getCreateTime());
                ctx.getEsCi().setCiVersion(dbCIInfo.getCiVersion());
                ctx.setOldAttrs(coverToAttrs(dbCIInfo.getAttrs()));
                ctx.getEsCi().setLocalVersion(dbCIInfo.getLocalVersion());
                ctx.getEsCi().setPublicVersion(selectVersion(ctx.getEsCi(), dbCIInfo));
                if (!CheckAttrUtil.checkAttrMapEqual(ctx.getStdMap(), ctx.getOldAttrs())) {
                    ctx.getEsCi().setCiVersion(String.valueOf(Long.parseLong(dbCIInfo.getCiVersion()) + 1L));
                    ctx.getEsCi().setLocalVersion(dbCIInfo.getLocalVersion() + 1L);
                    ctx.setChange(true);
                }
            }
            ctx.getEsCi().setClassId(ctx.getClassId());
            ctx.getEsCi().setOwnerCode(ctx.getOwnerCode());
            ctx.getEsCi().setDataStatus(1);
            ctx.getEsCi().setState(ctx.getState());
            ctx.getEsCi().setHashCode(ctx.getHashCode());
            ctx.getEsCi().setModifier(loginCode);
            if (ctx.getState() == 0 && BinaryUtils.isEmpty(ctx.getPrimaryKeys())) {
                ctx.setPrimaryKeys(Collections.singletonList(ctx.getCiCode()));
            }
            ctx.getEsCi().setCiPrimaryKey(JSON.toString(ctx.getPrimaryKeys()));
        }
        removeList.forEach(paramsContext::remove);
        List<ESCIInfo> esCiList = paramsContext.values().stream().map(SavePrivateBatchCIContext::getEsCi).collect(Collectors.toList());
        esCiSvc.saveOrUpdateCiList(esCiList);
        esCiSvc.transCIAttrs(esCiList, true);
        return paramsContext;
    }

    @Override
    public Map<String, SavePrivateBatchCIContext> copyCiListByIds(List<ESCIInfo> copyList, String ownerCode) {
        return this.extendCopyCiListByIds(copyList, ownerCode, DEFAULT_COPY_POSTFIX);
    }

    @Override
    public Map<String, SavePrivateBatchCIContext> extendCopyCiListByIds(List<ESCIInfo> copyList, String ownerCode, String postfix) {
        if (BinaryUtils.isEmpty(copyList)) {
            return Collections.emptyMap();
        }
        Assert.notNull(ownerCode, "X_PARAM_NOT_NULL${name:ownerCode}");
        Set<Long> ids = copyList.stream().map(ESCIInfo::getId).collect(Collectors.toSet());
        ESCISearchBean bean = new ESCISearchBean();
        bean.setPageNum(1);
        bean.setPageSize(ids.size());
        bean.setIds(new ArrayList<>(ids));
        bean.setOwnerCode(ownerCode);
        Page<ESCIInfo> page = searchESCIByBean(bean);
        List<ESCIInfo> ciList = page.getData();
        if (BinaryUtils.isEmpty(ciList)) {
            throw new BinaryException("被复制对象未找到");
        }
        Set<Long> classIds = ciList.stream().map(ESCIInfo::getClassId).collect(Collectors.toSet());

        Map<Long, ESCIClassInfo> classMap = esClsSvc.selectMapByQuery(1, classIds.size(), QueryBuilders.termsQuery("id", classIds));
        // 检验当前分类主键类型 规则仅允许主键为字符串类型数据进行复制 拓展copy上层含有校验 不需要进行校验
        if (DEFAULT_COPY_POSTFIX.equals(postfix)) {
            checkClassPKType(classMap.values());
        }
        Map<String, SavePrivateBatchCIContext> paramsContext = new HashMap<>();
        for (ESCIInfo item : ciList) {
            long originCiId = item.getId();
            String originCiCode = item.getCiCode();
            item.setId(ESUtil.getUUID());
            item.setCiCode(String.valueOf(item.getId()));
            item.setOwnerCode(ownerCode);
            Long classId = item.getClassId();
            String ciCode = item.getCiCode();

            SavePrivateBatchCIContext context = paramsContext.computeIfAbsent(ciCode, k -> new SavePrivateBatchCIContext(ciCode, classId, item));
            Map<String, Object> attrs = item.getAttrs();
            context.setId(item.getId());
            context.setOriginCiId(originCiId);
            context.setOriginCiCode(originCiCode);
//            context.setDiagramId(item.getDiagramId());
            context.setAdd(Boolean.TRUE);
            context.setOwnerCode(ownerCode);

            ESCIClassInfo ciClass = classMap.get(classId);
            if (ciClass == null) {
                throw MessageException.i18n("BS_MNAME_CLASS_NOT_EXSIT");
            }
            context.setClassStdCode(ciClass.getClassStdCode());
            context.setClassName(ciClass.getClassName());
            // 校验CI属性-当前及父类属性
            List<CcCiAttrDef> attrDefs = classMap.get(classId).getCcAttrDefs();
            //业务主键key
            List<String> ciPKAttrDefNames = CommUtil.getCiPKAttrDefNames(attrDefs);

            CIState state = commSvc.generateStateAndValidAttrs(attrDefs, coverToAttrs(attrs), ciPKAttrDefNames);
            item.setState(state.val());
            context.setState(state.val());
            context.setAttrDefs(attrDefs);
            List<String> ciPrimaryKeys = Collections.emptyList();
            int hashCode1 = 0;
            if (state == CIState.CREATE_INIT) {
                context.setAttrsObj(attrs);
                context.setAttrsStr(coverToAttrs(attrs));
                Map<String, String> stdMap = CheckAttrUtil.toStdMap(context.getAttrsStr());
                context.setStdMap(stdMap);
            } else {
                //Map<String, String> attrs, Collection<String> majorAttrDefNames, List<CcCiAttrDef> attrDefs
                String copyKey = CommUtil.findCopyKey(attrs, ciPKAttrDefNames, attrDefs);
                Map<String, String> attrsStr = coverToAttrs(attrs);
                for (int step = 1; step < 1000; step += 10) {
                    List<String> values = new ArrayList<>(10);
                    List<Integer> hashCodes = new ArrayList<>(10);
                    for (int index = step; index < step + 10; index++) {
                        String val = extendTryCopyVal(attrs, copyKey, index, postfix);
                        attrsStr.put(copyKey, val);
                        int hashCode = CommUtil.getCiMajorHashCode(attrsStr, ciPKAttrDefNames, context.getClassStdCode(), ownerCode);
                        values.add(val);
                        hashCodes.add(hashCode);
                    }
                    BoolQueryBuilder hashCodeQuery = QueryBuilders.boolQuery();
                    hashCodeQuery.must(QueryBuilders.termsQuery("hashCode", hashCodes));
                    hashCodeQuery.must(QueryBuilders.termQuery("classId", item.getClassId()));
                    hashCodeQuery.must(QueryBuilders.termQuery(OWNER_CODE_KEYWORD, ownerCode));
                    List<CcCiInfo> liveCis = esCiSvc.getCIInfoPageByQuery(1, hashCodes.size() * 100, hashCodeQuery, false).getData();
                    if (BinaryUtils.isEmpty(liveCis)) {
                        CommUtil.ensurePKAttr(attrs, copyKey, values.get(0));
                        break;
                    } else {
                        Set<String> repeatValues = new HashSet<>();
                        for (CcCiInfo liveCi : liveCis) {
                            repeatValues.add(String.valueOf(liveCi.getAttrs().get(copyKey)));
                        }
                        values.removeAll(repeatValues);
                        if (values.isEmpty()) {
                            continue;
                        }
                        CommUtil.ensurePKAttr(attrs, copyKey, values.get(0));
                        break;
                    }
                }
                for (CcCiAttrDef attrDef : attrDefs) {
                    if(attrDef.getProType()==150){
                        CiCodeDto ciCodeDto = new CiCodeDto();
                        ciCodeDto.setDefVal(attrDef.getDefVal());
                        ciCodeDto.setClassCode(classMap.get(classId).getClassCode());
                        ciCodeDto.setProName(attrDef.getProName());
                        ciCodeDto.setStartNum(attrDef.getEnumValues());
                        String enCodeNum = classApiSvc.getEnCodeNum(ciCodeDto);
                        attrs.put(attrDef.getProStdName(),enCodeNum);
                    }
                }
                context.setAttrsObj(attrs);
                context.setAttrsStr(coverToAttrs(attrs));
                Map<String, String> stdMap = CheckAttrUtil.toStdMap(context.getAttrsStr());
                context.setStdMap(stdMap);
                hashCode1 = CommUtil.getCiMajorHashCode(context.getAttrsStr(), ciPKAttrDefNames, context.getClassStdCode(), ownerCode);
                ciPrimaryKeys = CommUtil.getCiPrimaryKeys(context.getClassStdCode(), context.getAttrsStr(), ciPKAttrDefNames);
            }
            context.setPrimaryKeys(ciPrimaryKeys);
            context.setHashCode(hashCode1);
        }
        List<SavePrivateBatchCIContext> contexts = paramsContext.values().stream().filter(each -> each.getState() > 0).collect(Collectors.toList());
        if (!BinaryUtils.isEmpty(contexts)) {
            Map<Integer, List<SavePrivateBatchCIContext>> hashCodesMap = contexts.stream().collect(Collectors.groupingBy(SavePrivateBatchCIContext::getHashCode));
            BoolQueryBuilder hashCodeQuery = QueryBuilders.boolQuery();
            hashCodeQuery.must(QueryBuilders.termsQuery("hashCode", hashCodesMap.keySet()));
            hashCodeQuery.must(QueryBuilders.termQuery(OWNER_CODE_KEYWORD, ownerCode));
            List<CcCiInfo> liveCis = esCiSvc.getCIInfoPageByQuery(1, hashCodesMap.size() * 100, hashCodeQuery, false).getData();
            for (CcCiInfo cInfo : liveCis) {
                List<SavePrivateBatchCIContext> contextList = hashCodesMap.get(cInfo.getCi().getHashCode());
                if (BinaryUtils.isEmpty(contextList)) {
                    continue;
                }
                for (SavePrivateBatchCIContext repeatCtx : contextList) {
                    if (compareCiPrimaryKeys(repeatCtx.getPrimaryKeys(), JSON.toList(cInfo.getCi().getCiPrimaryKey(), String.class))) {
                        boolean idEqual = repeatCtx.getId() != null && repeatCtx.getId().longValue() == cInfo.getCi().getId().longValue();
                        boolean classIdEqual = repeatCtx.getClassId().longValue() == cInfo.getCi().getClassId().longValue();
                        boolean isUpdate = idEqual || classIdEqual;
                        if (isUpdate) {
                            if (repeatCtx.getId() != null) {
                                continue;
                            }
                            repeatCtx.setId(cInfo.getCi().getId());
                            repeatCtx.setAdd(false);
                        } else {
//                            throw MessageException.i18n("BS_MNAME_RECORD_CONTAINS");
                            String loginCode = SysUtil.getCurrentUserInfo().getLoginCode();
                            if(ownerCode.equals(loginCode)){
                                throw new ServiceException("该对象已存在于私有库,请从私有库拖入该对象！");
                            } else {
                                throw new ServiceException("该对象已存在于协作共享库,请从协作共享库拖入该对象！");
                            }
                        }
                    }
                }
            }
        }

        for (SavePrivateBatchCIContext ctx : paramsContext.values()) {
            // 属性过滤，只保存定义过的属性
            Iterator<String> itAttr = ctx.getStdMap().keySet().iterator();
            List<String> defNames = ctx.getAttrDefs().stream().map(CcCiAttrDef::getProStdName).collect(Collectors.toList());
            while (itAttr.hasNext()) {
                String attrName = itAttr.next();
                // 属性不包含在定义的属性，并且不是下划线开头结尾的字段，去掉
                if (!defNames.contains(attrName) && !attrName.matches("_([\\d\\D]*?)_")) {
                    itAttr.remove();
                }
            }
            // 组装CI
            long uuid = ESUtil.getUUID();
            ctx.getEsCi().setId(uuid);
            ctx.getEsCi().setCiCode(ctx.getCiCode());
            ctx.getEsCi().setCreator(ownerCode);
            ctx.getEsCi().setCreateTime(ESUtil.getNumberDateTime());
            ctx.getEsCi().setCiVersion(String.valueOf(1));
            ctx.getEsCi().setClassId(ctx.getClassId());
            ctx.getEsCi().setOwnerCode(ctx.getOwnerCode());
            ctx.getEsCi().setDataStatus(1);
            ctx.getEsCi().setState(ctx.getState());
            ctx.getEsCi().setHashCode(ctx.getHashCode());
            ctx.getEsCi().setModifier(ownerCode);
            ctx.getEsCi().setLocalVersion(1L);
            ctx.getEsCi().setPublicVersion(0L);
            if (ctx.getState() == 0 && BinaryUtils.isEmpty(ctx.getPrimaryKeys())) {
                ctx.setPrimaryKeys(Collections.singletonList(ctx.getCiCode()));
            }
            ctx.getEsCi().setCiPrimaryKey(JSON.toString(ctx.getPrimaryKeys()));
        }
        List<ESCIInfo> esCiList = paramsContext.values().stream().map(SavePrivateBatchCIContext::getEsCi).collect(Collectors.toList());
        esCiSvc.saveOrUpdateCiList(esCiList);
        esCiSvc.transCIAttrs(esCiList, true);
        return paramsContext;
    }

    private Map<String, String> coverToAttrs(Map<String, Object> attrs) {
        Map<String, String> newAttrs = new HashMap<>();
        if (BinaryUtils.isEmpty(attrs)) {
            return newAttrs;
        }
        attrs.forEach((k, v) -> {
            if (!BinaryUtils.isEmpty(v)) {
                newAttrs.put(k, String.valueOf(v));
            }
        });
        return newAttrs;
    }


    @Override
    public ImportSheetMessage saveOrUpdateCiBath(Long domainId, CiClassSaveInfo saveInfo) {
        BinaryUtils.checkEmpty(saveInfo, "saveInfo");
        BinaryUtils.checkEmpty(saveInfo.getClassId(), "classId");
        SysUser loginUser = SysUtil.getCurrentUserInfo();
        saveInfo.setOwnerCode(loginUser.getLoginCode());
        ImportSheetMessage detailedResult = ImportSheetMessage.builder().build();
        List<ImportRowMessage> rowMessages = new ArrayList<>();
        // 统计信息
        int successCount = 0;
        int failCount = 0;
        int totals = 0;
        int ignores = 0;
        int inserts = 0;
        int updates = 0;
        List<String> sucessCIPKs = Lists.newArrayList();
        List<CcCiRecord> saveCiRecords = saveInfo.getRecords();
        // 保存CI
        if (!BinaryUtils.isEmpty(saveCiRecords)) {
            totals = saveCiRecords.size();
            Long classId = saveInfo.getClassId();
            Long sourceId = BinaryUtils.isEmpty(saveInfo.getSourceId()) ? 1L : saveInfo.getSourceId();
            // 获取当前及父级分类属性
            List<CcCiAttrDef> attrDefs = esClsSvc.getAllDefsByClassId(domainId, classId);
            List<String> defNames = attrDefs.stream().map(CcCiAttrDef::getProStdName).collect(Collectors.toList());
            ESCIClassInfo classInfo = esClsSvc.getById(classId);
            // 查询要更新的数据
            Map<String, CcCiInfo> ciCodeMap = new HashMap<>();
            Map<Long, String> idToCode = new HashMap<>();
            Set<String> ciCodeSet = saveCiRecords.stream().map(CcCiRecord::getCiCode).filter(code -> !BinaryUtils.isEmpty(code)).collect(Collectors.toSet());
            Set<Long> ciIds = saveCiRecords.stream().map(CcCiRecord::getCiId).filter(id -> !BinaryUtils.isEmpty(id)).collect(Collectors.toSet());
            if (!(BinaryUtils.isEmpty(ciCodeSet) && BinaryUtils.isEmpty(ciIds))) {
                BoolQueryBuilder query = QueryBuilders.boolQuery();
                if (!BinaryUtils.isEmpty(ciCodeSet)) {
                    query.should(QueryBuilders.termsQuery("ciCode.keyword", ciCodeSet));
                }
                if (!BinaryUtils.isEmpty(ciIds)) {
                    query.should(QueryBuilders.termsQuery("id", ciIds));
                }
                List<CcCiInfo> esInfos = esCiSvc.getCIInfoPageByQuery(1, saveCiRecords.size(), query, false).getData();
                if (!BinaryUtils.isEmpty(esInfos)) {
                    for (CcCiInfo esCiInfo : esInfos) {
                        ciCodeMap.put(esCiInfo.getCi().getCiCode(), esCiInfo);
                        idToCode.put(esCiInfo.getCi().getId(), esCiInfo.getCi().getCiCode());
                    }
                }
            }
            // 获取数据字典值
            Map<String, List<String>> dictValuesMap = this.getExterDictValues(domainId, attrDefs);

            List<CcCiInfo> ciInfosList = new ArrayList<CcCiInfo>();
            List<Integer> hashCodes = new ArrayList<>();
            Map<String, Integer> keyToIndex = new HashMap<>();
            List<String> saveCodes = new ArrayList<>();
            List<String> savePrimaryKeys = new ArrayList<>();
            Iterator<CcCiRecord> it = saveCiRecords.iterator();
            Map<String, ESCIOperateLog> updateLogMap = new HashMap<>();
            recordLoop:
            while (it.hasNext()) {
                boolean isUpdate = false;
                CcCiRecord record = it.next();
                Map<String, String> attrs = record.getAttrs();
                // 校验属性
                Map<String, Integer> errMsg = commSvc.validAttrs(attrDefs, attrs, true);
                if (!BinaryUtils.isEmpty(errMsg)) {
                    List<ImportCellMessage> cellMessages = new ArrayList<>();
                    errMsg.forEach((msg, errType) -> {
                        ImportCellMessage cellMessage = new ImportCellMessage();
                        cellMessage.setErrorType(errType);
                        cellMessage.setErrorDesc(msg);
                        cellMessages.add(cellMessage);
                    });
                    ImportRowMessage rowMessage = new ImportRowMessage();
                    rowMessage.setRowNum(record.getIndex());
                    rowMessage.setMessageItems(cellMessages);
                    rowMessages.add(rowMessage);
                    failCount++;
                    it.remove();
                    continue;
                }
                Map<String, String> stdMap = toStdMap(attrs);
                // 校验数据字典类型
                if (!BinaryUtils.isEmpty(dictValuesMap)) {
                    Iterator<Entry<String, List<String>>> dictIt = dictValuesMap.entrySet().iterator();
                    while (dictIt.hasNext()) {
                        Entry<String, List<String>> next = dictIt.next();
                        String key = next.getKey();
                        List<String> values = next.getValue();
                        String val = stdMap.get(key);
                        if (record.getCiId() == null && !BinaryUtils.isEmpty(val) && !values.contains(val)) {
                            rowMessages.add(this.buildRowMessage(record.getIndex(), CheckAttrUtil.FAILURE, "属性[" + key + "]引用值[" + val + "]不存在"));
                            ignores++;
                            it.remove();
                            continue recordLoop;
                        }
                    }
                }
                String ciCode = record.getCiCode();
                // 兼容无ciCode时更新的情况
                if (BinaryUtils.isEmpty(ciCode) && record.getCiId() != null) {
                    ciCode = idToCode.get(record.getCiId());
                }
                // 校验ciCode，同一分类下，ciCode相同更新
                if (saveCodes.contains(ciCode)) {
                    rowMessages.add(this.buildRowMessage(record.getIndex(), CheckAttrUtil.EXIST, "ciCode【" + ciCode + "】重复"));
                    ignores++;
                    it.remove();
                    continue;
                } else if (ciCodeMap.containsKey(ciCode)) {
                    CcCiInfo ciInfo = ciCodeMap.get(ciCode);
                    if (ciInfo.getCi().getClassId().longValue() == classId.longValue()) {
                        record.setCiId(ciInfo.getCi().getId());
                        updates++;
                        isUpdate = true;
                    } else {
                        rowMessages.add(this.buildRowMessage(record.getIndex(), CheckAttrUtil.EXIST, "ciCode【" + ciCode + "】重复"));
                        ignores++;
                        it.remove();
                        continue;
                    }
                }
                if (!BinaryUtils.isEmpty(ciCode)) {
                    saveCodes.add(ciCode);
                }
                List<String> ciPKAttrDefNames = CommUtil.getCiPKAttrDefNames(attrDefs);
                // 获取所属分类下的业务主键值的hashCode
                Integer hashCode;
                try {
                    hashCode = CommUtil.getCiMajorHashCode(attrs, ciPKAttrDefNames, classInfo.getClassStdCode(), saveInfo.getOwnerCode());
                } catch (Exception e) {
                    failCount++;
                    rowMessages.add(this.buildRowMessage(record.getIndex(), CheckAttrUtil.FAILURE, "业务主键生成失败"));
                    continue;
                }
                // 获取当前CI的业务主键值
                List<String> ciPrimaryKeys = CommUtil.getCiPrimaryKeys(classInfo.getClassStdCode(), attrs, ciPKAttrDefNames);
                String primaryKey = JSON.toString(ciPrimaryKeys);
                if (savePrimaryKeys.contains(primaryKey)) {
                    ignores++;
                    rowMessages.add(this.buildRowMessage(record.getIndex(), CheckAttrUtil.EXIST, "业务主键【" + ciPrimaryKeys + "】重复"));
                    continue;
                } else {
                    savePrimaryKeys.add(primaryKey);
                }
                CcCiInfo ciInfo = new CcCiInfo();
                // 属性过滤，只保存定义过的属性
                Iterator<String> itAttr = stdMap.keySet().iterator();
                while (itAttr.hasNext()) {
                    String attrName = itAttr.next();
                    // 属性不包含在定义的属性，并且不是下划线开头结尾的字段，去掉
                    if (!defNames.contains(attrName) && !attrName.matches("_([\\d\\D]*?)_")) {
                        itAttr.remove();
                    }
                }
                // CI 属性大小写转换
                ciInfo.setAttrs(stdMap);
                CcCi ci = new CcCi();
                if (isUpdate) {
                    CcCiInfo oldCi = ciCodeMap.get(ciCode);
                    ci.setCreateTime(oldCi.getCi().getCreateTime());
                    ci.setCiVersion(oldCi.getCi().getCiVersion());
                    ci.setLocalVersion(oldCi.getCi().getLocalVersion());
                    ci.setPublicVersion(oldCi.getCi().getPublicVersion());
                    // 属性合并
                    Map<String, String> combineAttrs = new HashMap<>();
                    combineAttrs.putAll(oldCi.getAttrs());
                    combineAttrs.putAll(stdMap);
                    ciInfo.setAttrs(combineAttrs);
                    if (!CheckAttrUtil.checkAttrMapEqual(combineAttrs, oldCi.getAttrs())) {
                        ci.setCiVersion(String.valueOf(Long.parseLong(oldCi.getCi().getCiVersion()) + 1L));
                        ci.setLocalVersion(oldCi.getCi().getLocalVersion() + 1L);
                    }
                    ci.setOwnerCode(oldCi.getCi().getOwnerCode());
                } else {
                    ci.setCreator(loginUser == null ? "system" : loginUser.getLoginCode());
                    ci.setCreateTime(ESUtil.getNumberDateTime());
                    ci.setOwnerCode(loginUser.getLoginCode());
                    ci.setLocalVersion(1L);
                }
                ci.setId(record.getCiId() == null ? ESUtil.getUUID() : record.getCiId());
                ci.setCiCode(ciCode);
                ci.setClassId(classId);
                ci.setSourceId(sourceId);
                ci.setDataStatus(1);
                ci.setHashCode(hashCode);
                ci.setCiPrimaryKey(primaryKey);
                ci.setCreateTime(ESUtil.getNumberDateTime());
                ci.setDomainId(domainId == null ? 1L : domainId);
                ci.setState(CIState.CREATE_COMPLETE.val());
                ciInfo.setCi(ci);
                ciInfosList.add(ciInfo);
                hashCodes.add(hashCode);
                keyToIndex.put(ci.getCiPrimaryKey(), record.getIndex());
                if (isUpdate) {
                    ESCIOperateLog ciLog = buildLogRecord(sourceId, SysUtil.StaticUtil.LOG_DYNAMIC_UPDATE, attrDefs, ciCodeMap.get(ciCode).getAttrs(), stdMap, classInfo.getClassName(), ci);
                    updateLogMap.put(ciCode, ciLog);
                }
            }
            List<String> repeatKeys = new ArrayList<>();
            // 校验hashCode是否重复
            Map<String, Integer> res = this.checkRecordsByHashCode(ciInfosList, repeatKeys, updateLogMap);
            ignores += res.get("ignore") == null ? 0 : res.get("ignore");
            updates += res.get("update") == null ? 0 : res.get("update");
            if (!BinaryUtils.isEmpty(repeatKeys)) {
                for (String str : repeatKeys) {
                    rowMessages.add(this.buildRowMessage(keyToIndex.get(str), CheckAttrUtil.EXIST, "业务主键【" + str + "】重复"));
                }
            }
            // 保存CI操作日志
            List<ESCIOperateLog> ciLogs = new ArrayList<>(updateLogMap.values());
            for (ESCIOperateLog ciLog : ciLogs) {
                ciLog.setSourceId(sourceId);
                ciLog.setCiClassName(classInfo.getClassName());
                ciLog.setProNames(attrDefs.stream().map(CcCiAttrDef::getProStdName).collect(Collectors.toList()));
                ciLog.setOperator(loginUser == null ? "system" : loginUser.getLoginCode());
            }
            Set<String> keySet = updateLogMap.keySet();
            if (ciInfosList.size() > keySet.size()) {
                ciInfosList.forEach(ciInfo -> {
                    if (BinaryUtils.isEmpty(ciInfo.getCi().getCiCode())) {
                        ciInfo.getCi().setCiCode(String.valueOf(ciInfo.getCi().getId()));
                    }
                    String ciCode = ciInfo.getCi().getCiCode();
                    if (!keySet.contains(ciCode)) {
                        ciLogs.add(buildLogRecord(sourceId, SysUtil.StaticUtil.LOG_DYNAMIC_INSERT, attrDefs, null, ciInfo.getAttrs(), classInfo.getClassName(), ciInfo.getCi()));
                    }
                });
            }
            Map<String, Object> saveOrUpdateMsg = esCiSvc.saveOrUpdateCIBatch(ciInfosList, false);
            ciInfosList.forEach(ci -> {
                sucessCIPKs.add(ci.getCi().getCiPrimaryKey());
            });
            if (!BinaryUtils.isEmpty(saveOrUpdateMsg.get("failCount"))) {
                failCount += (Integer) saveOrUpdateMsg.get("failCount");
            }
            inserts = (totals - failCount - updates - ignores);
            successCount = inserts + updates;
            if (!BinaryUtils.isEmpty(saveOrUpdateMsg.get("errMessge"))) {
                String errStr = JSON.toString(saveOrUpdateMsg.get("errMessge"));
                // 入库失败
                rowMessages.add(this.buildRowMessage(1, CheckAttrUtil.FAILURE, "入库失败：" + errStr));
            }
            this.saveCIOperateLogBatch(ciLogs);
        }
        // 汇总整理结果
        detailedResult.setSuccessNum(successCount);
        detailedResult.setFailNum(failCount);
        detailedResult.setInsertNum(inserts);
        detailedResult.setUpdateNum(updates);
        detailedResult.setIgnoreNum(ignores);
        detailedResult.setTotalNum(totals);
        detailedResult.setSucessCIPks(sucessCIPKs);
        if (!BinaryUtils.isEmpty(rowMessages)) {
            Collections.sort(rowMessages, FileUtil.ExcelUtil.getRowMessageComparator(rowMessages));
            detailedResult.setRowMessages(rowMessages);
        }
        return detailedResult;
    }

    @Override
    public CcCiInfo getCiInfoById(Long id) {
        return esCiSvc.getCiInfoById(id);
    }

    @Override
    public CiGroupPage queryPageByIndex(Long domainId, Integer pageNum, Integer pageSize, CiQueryCdt cdt, Boolean hasClass) {
        return esCiSvc.queryPageByIndex(pageNum, pageSize, (CiQueryCdtExtend) cdt, hasClass);

    }

    @Override
    public CiGroupPage queryPageBySearchBean(ESCISearchBean bean, Boolean hasClass) {
        if(OWNER_CODE.equals(bean.getSortField())){
            bean.setSortField(OWNER_CODE_KEYWORD);
        }
        CiGroupPage ciGroupPage = esCiSvc.queryPageBySearchBean(bean, hasClass);

        // The current 3D scene, open the 3D model path to add
        this.addModelIconPath(bean, ciGroupPage);
        Set<String> set = new HashSet<>();
        ciGroupPage.getData().forEach(info -> {
            set.add(info.getCi().getOwnerCode());
        });
        String[] codes = set.toArray(new String[set.size()]);
        if (codes.length > 0) {
            CSysUser user = new CSysUser();
            user.setLoginCodes(codes);
            List<SysUser> sysUserByCdt = userSvc.getSysUserByCdt(user);
            sysUserByCdt.forEach(sysUser -> {
                ciGroupPage.getData().forEach(info -> {
                    if (sysUser.getLoginCode().equals(info.getCi().getOwnerCode())) {
                        Map<String, String> attrs = info.getAttrs();
                        attrs.put("所属用户", sysUser.getUserName());
                    }
                });
            });
        }

        return ciGroupPage;
    }


    @Override
    public List<CcCi> queryCiList(Long domainId, CCcCi cdt, String orders, Boolean isAsc) {
        List<CcCi> ciList = new ArrayList<>();
        if (cdt == null) {
            cdt = new CCcCi();
        }
        cdt.setDomainId(domainId);
        orders = BinaryUtils.isEmpty(orders) ? "modifyTime" : orders;
        List<CcCiInfo> ciInfos = esCiSvc.queryCiInfoList(cdt, orders, isAsc, false);
        for (CcCiInfo ciInfo : ciInfos) {
            ciList.add(ciInfo.getCi());
        }
        return ciList;
    }

    @Override
    public List<ESCIInfo> queryESCIInfoList(Long domainId, CCcCi cdt, String orders, Boolean isAsc) {
        if (cdt == null) {
            cdt = new CCcCi();
        }
        cdt.setDomainId(domainId);
        orders = BinaryUtils.isEmpty(orders) ? "modifyTime" : orders;
        return esCiSvc.queryESCiInfoList(cdt, orders, isAsc);
    }

    @Override
    public List<CcCiInfo> queryCiInfoList(Long domainId, CCcCi cdt, String orders, Boolean isAsc, Boolean hasClass) {
        if (cdt == null) {
            cdt = new CCcCi();
        }
        cdt.setDomainId(domainId);
        orders = BinaryUtils.isEmpty(orders) ? "modifyTime" : orders;
        return esCiSvc.queryCiInfoList(cdt, orders, isAsc, hasClass);
    }

    @Override
    public List<ESCIInfo> getESCIInfoListByCIPrimaryKeys(Long domainId, List<List<String>> ciPrimaryKeys) {
        if (BinaryUtils.isEmpty(ciPrimaryKeys)) {
            return Collections.emptyList();
        }
        return esCiSvc.getCIInfoListByCIPrimaryKeys(ciPrimaryKeys);
    }

    @Override
    public List<CcCiInfo> getCIInfoListByCIPrimaryKeys(Long domainId, List<List<String>> ciPrimaryKeys) {
        List<ESCIInfo> esciInfos = this.getESCIInfoListByCIPrimaryKeys(domainId, ciPrimaryKeys);
        return commSvc.transEsInfoList(esciInfos, false);
    }

    @Override
    public Page<CcCiInfo> queryCiInfoPage(Long domainId, Integer pageNum, Integer pageSize, CCcCi cdt, String orders, Boolean isAsc, Boolean hasClass) {
        if (cdt == null) {
            cdt = new CCcCi();
        }
        cdt.setDomainId(domainId);
        return esCiSvc.queryCiInfoPage(pageNum, pageSize, cdt, orders, isAsc, hasClass);
    }

    @Override
    public CcCiSearchPage searchCIByCdt(int pageNum, int pageSize, CCcCi bean) {
        if (BinaryUtils.isEmpty(bean)) {
            bean = new CCcCi();
        }
        return esCiSvc.searchCIByCdt(pageNum, pageSize, bean);
    }

    @Override
    public Page<ESCIInfo> searchESCIByBean(ESCISearchBean bean) {
        return esCiSvc.searchESCIByBean(bean);
    }

    @Override
    public CcCiSearchPage searchCIByBean(ESCISearchBean bean) {
        if (BinaryUtils.isEmpty(bean)) {
            bean = new ESCISearchBean();
            bean.setPageNum(1);
            bean.setPageSize(30);
        }
        return esCiSvc.searchCIByBean(bean);
    }

    @Override
    public Integer updateESCIInfoBatch(List<ESCIInfo> esCiInfoList) {
        return esCiSvc.updateESCIInfoBatch(esCiInfoList);
    }

    @Override
    public ImportResultMessage importCiByCiClsIds(MultipartFile file, Long classId) {
        // 校验分类
        Assert.notNull(classId, "X_PARAM_NOT_NULL${name:classId}");
        ESCIClassInfo classInfo = esClsSvc.getById(classId);
        Assert.notNull(classInfo, "BS_MNAME_CLASS_NOT_EXSIT");
        String className = classInfo.getClassName();
        ImportExcelMessage imResult = this.importCiExcel(file);
        String sheetName = null;
        for (String imSheetName : imResult.getSheetNames()) {
            if (className.equals(imSheetName)) {
                // sheet未填写领域的情况
                sheetName = imSheetName;
            }
        }
        if (sheetName == null) {
            throw MessageException.i18n("BS_MNAME_CLASS_DATA_ERROR", "{\"field\":" + className + "}");
        }
        CiExcelInfoDto infoDto = CiExcelInfoDto.builder().fileName(imResult.getFileName()).dirId(classInfo.getDirId()).sheetNames(Collections.singletonList(sheetName)).build();
        return this.importCiByClassBatch(classInfo.getDomainId(), infoDto, false);
    }

    @Override
    public ResponseEntity<byte[]> exportCiOrClass(ExportCiDto exportDto) {
        ValidDtoUtil.valid(exportDto);
        ResponseEntity<byte[]> res = null;
        if (exportDto == null) {
            exportDto = new ExportCiDto();
        }
        if (exportDto.isDataMode()) {
            // 处理准确数据模式前置动作
            Set<Long> ciIds = exportDto.getCiIds();
            Page<ESCIInfo> cis = esCiSvc.getListByQuery(1, ciIds.size(), QueryBuilders.termsQuery("id", ciIds));
            Assert.isTrue(cis.getData().size() == ciIds.size(), "指定的ciIds[" + com.alibaba.fastjson.JSON.toJSONString(ciIds) + "]有不存在数据");
            Set<Long> clsIds = cis.getData().stream().collect(Collectors.groupingBy(ESCIInfo::getClassId)).keySet();
            exportDto.setCiClassIds(clsIds);
        }
        Set<Long> classIds = exportDto.getCiClassIds();
        // 默认不导出类定义
        Integer hasClsDef = exportDto.getHasClsDef();
        // 默认只导出分类
        Integer hasData = exportDto.getHasData();
        // 默认导出全部类定义
        BoolQueryBuilder query = QueryBuilders.boolQuery();
        if (!BinaryUtils.isEmpty(classIds)) {
            query.must(QueryBuilders.termsQuery("id", classIds));
        }
        List<ESCIClassInfo> ciClassInfos = esClsSvc.getListByQuery(query);
        // 导出类定义
        String fileName = FileUtil.ExcelUtil.getExportFileName(new CIExportLabel().getCIFileName(true), CommUtil.EXCEL07_XLSX_EXTENSION, false);
        File export = null;
        Workbook swb = null;
        try {
            String path = URLDecoder.decode(ResourceUtils.getURL("classpath:").getPath(), "utf-8");
            File exportDir = new File(path + "/static/download");
            if (!exportDir.exists()) {
                exportDir.mkdirs();
            }
            export = new File(exportDir, "CI-" + CommUtil.EXPORT_TIME_FORMAT.format(new Date()));
            export.mkdir();
            InputStream is = this.getClass().getResourceAsStream("/static_res/ci_new_data_template.xlsx");
            swb = new SXSSFWorkbook(new XSSFWorkbook(is));
        } catch (Exception e) {
            log.error("生成excel失败：",e);
            throw new BinaryException("ci导出报错");
        }
        // 导出类定义
        if (hasClsDef == 1) {
            clsSvc.exportCIClassDef(swb, ciClassInfos);
        }
        // 导出类数据，只导出勾选的分类
        boolean dataNull = true;
        Integer dataCount = 0;
        Integer excelCount = 0;
        if (!(BinaryUtils.isEmpty(classIds) || BinaryUtils.isEmpty(ciClassInfos))) {
            // 标记CI数据是否为空，只要查到数据则设为false
            // 导出数据
            Long domainId = SysUtil.getCurrentUserInfo().getDomainId();
            fileName = FileUtil.ExcelUtil.getExportFileName(new CIExportLabel().getCIFileName(false), CommUtil.EXCEL07_XLSX_EXTENSION, true);
            for (ESCIClassInfo classInfo : ciClassInfos) {
                List<CcCiAttrDef> attrDefs = esClsSvc.getAllDefsByClassId(domainId, classInfo.getId());
                if (BinaryUtils.isEmpty(attrDefs)) {
                    continue;
                }
                // 定义集合有序保存列头值
                List<String> titleCellValues = new ArrayList<String>();
                Set<String> reqCellValues = new HashSet<String>();
                // 获取业务主键属性定义
                Set<String> pkbCellValues = new HashSet<String>();
                // 指定ciCode列
                String majorCellValue = SysUtil.StaticUtil.CICODE_LABEL;
                // 默认第一列为ciCode列
                titleCellValues.add(majorCellValue);
                for (CcCiAttrDef attrDef : attrDefs) {
                    if (attrDef.getIsMajor() == 1) {
                        pkbCellValues.add(attrDef.getProName());
                        reqCellValues.add(attrDef.getProName());
                    } else if (attrDef.getIsRequired() == 1) {
                        reqCellValues.add(attrDef.getProName());
                    }
                    // 3D模型属性校验
                    int proType = attrDef.getProType();

                    if (!isShow3dAttribute && proType == AttrNameKeyEnum.MODEL.getType()) {
                        continue;
                    }
                    titleCellValues.add(attrDef.getProName());

                }
                if (pkbCellValues.isEmpty()) {
                    continue;
                }
                String sheetName = CiExcelUtil.convertSheetNameSpecialChar(classInfo.getClassName());
                // 创建Sheet并设置标题行
                Sheet sheet = FileUtil.ExcelUtil.createExcelSheetAndTitle(swb, sheetName, titleCellValues, reqCellValues, pkbCellValues, majorCellValue);
                if (hasData == 1) {
                    // 兼容指定数据模式，只把指定的id填入到query中当筛选，简单处理沿用游标不做切换(照理说指定数据的就不应该这里再查了因为已经有了)
                    Map<String, Page<ESCIInfo>> rs = null;
                    BoolQueryBuilder exportQuery = QueryBuilders.boolQuery();
                    if (exportDto.isDataMode()) {
                        exportQuery.must(QueryBuilders.termsQuery("id", exportDto.getCiIds()));
                    }
                    exportQuery.must(QueryBuilders.termQuery(OWNER_CODE_KEYWORD, SysUtil.getCurrentUserInfo().getLoginCode()));
                    exportQuery.must(QueryBuilders.termQuery("classId", classInfo.getId()));
                    rs = esCiSvc.getScrollByQuery(1, 2000, exportQuery, "id", false);
                    List<ESCIInfo> ciInfos = new ArrayList<>();
                    if (rs != null) {
                        String scrollId = rs.keySet().iterator().next();
                        Page<ESCIInfo> page = rs.get(scrollId);
                        long ciCount = 0;
                        long total = page.getTotalRows();
                        int rowNum = 1;
                        ciInfos.addAll(page.getData());
                        while (ciCount < total) {
                            List<ESCIInfo> secList = esCiSvc.getListByScroll(scrollId);
                            ciInfos.addAll(secList);
                            dataNull = false;
                            // 提取正文值map
                            List<Map<String, String>> commentValues = new ArrayList<Map<String, String>>();
                            for (ESCIInfo info : ciInfos) {
                                Map<String, Object> attrs = info.getAttrs();
                                if (attrs == null || attrs.isEmpty()) {
                                    continue;
                                }
                                attrs.put(majorCellValue, info.getCiCode());

                                // 导出时, 3D模型属性路径移除
                                this.ciExport3DmodelAttrPathCheck(attrDefs, attrs);
                                Map<String, String> attrStr = com.alibaba.fastjson.JSON.parseObject(com.alibaba.fastjson.JSON.toJSONString(attrs), new TypeReference<Map<String, String>>() {
                                });
                                commentValues.add(attrStr);
                            }
                            // 持续写入正文
                            if (commentValues.size() > 0) {
                                FileUtil.ExcelUtil.writeExcelComment(swb, sheet, rowNum, titleCellValues, null, null, commentValues);
                            }
                            ciCount += ciInfos.size();
                            dataCount += ciInfos.size();
                            rowNum += ciInfos.size();
                            ciInfos.clear();
                            // 数据量达到了单个Excel最大量,且不是最后一页,则需要写完当前Excel再创建新的Excel来存储
                            if (dataCount >= CommUtil.EXCEL_MAX_DATA_COUNT) {
                                excelCount++;
                                try {
                                    String tempFileName = FileUtil.ExcelUtil.getExportFileName(new CIExportLabel().getCIFileName(false), CommUtil.EXCEL07_XLSX_EXTENSION, true);
                                    File output = new File(export, tempFileName);
                                    FileOutputStream fileOutputStream = new FileOutputStream(output);
                                    swb.write(fileOutputStream);
                                    swb.close();
                                    fileOutputStream.close();
                                } catch (Exception e) {
                                    throw BinaryUtils.transException(e, ServiceException.class);
                                }
                                dataCount = 0;
                                rowNum = 1;
                                swb = new SXSSFWorkbook();
                                sheet = FileUtil.ExcelUtil.createExcelSheetAndTitle(swb, sheetName, titleCellValues, reqCellValues, pkbCellValues, majorCellValue);
                            }
                        }
                        esCiSvc.clearScroll(scrollId);
                    }
                }
            }
        }
        File firstFile = null;
        // 末尾数据保存为一个excel
        if (dataCount > 0 || hasData != 1 || dataNull) {
            excelCount++;
            if (excelCount == 1) {
                firstFile = new File(export, fileName);
            }
            try {
                File output = new File(export, fileName);
                FileOutputStream fileOutputStream = new FileOutputStream(output);
                swb.write(fileOutputStream);
                swb.close();
                fileOutputStream.close();
            } catch (Exception e) {
                throw BinaryUtils.transException(e, ServiceException.class);
            }
        }
        if (excelCount > 1) {
            Compression.compressZip(new File(export.getPath()), new File(export.getPath() + ".zip"));
            res = ExcelUtil.returnRes(new File(export.getPath() + ".zip"));
        } else {
            res = ExcelUtil.returnRes(firstFile);
        }
        return res;
    }

    @Override
    public ImportExcelMessage importCiExcel(MultipartFile file) {
        // 校验文件
        boolean isXlsx = FileUtil.ExcelUtil.validExcelImportFile(file);
        ImportExcelMessage message = new ImportExcelMessage();
        message.setOriginalFileName(file.getOriginalFilename());
        // 上传文件存入资源池
        Long dateTimeFolder = ESUtil.getNumberDate();
        String dirPath = localPath + "/" + dateTimeFolder;
        File destFolder = new File(dirPath);
        if (!destFolder.exists()) {
            destFolder.mkdirs();
        }
        File excelFile = null;
        try {
            String fileName = CommUtil.getExportFileName("CI", isXlsx ? CommUtil.EXCEL07_XLSX_EXTENSION : CommUtil.EXCEL03_XLS_EXTENSION);
            excelFile = new File(dirPath + File.separator + fileName);
            FileUtil.writeFile(excelFile, file.getBytes());
            message.setFileName("/" + dateTimeFolder + "/" + fileName);
        } catch (Exception e) {
            log.error("文件上传异常");
            throw new MessageException(e.getMessage());
        }
        String clsDefKey = LanguageResolver.trans("BS_CI_CLASS_EXPORT_CLASS_DEFINITION");
        if (clsDefKey == null) {
            clsDefKey = "对象定义";
        }
        String importNotice = LanguageResolver.trans("BS_MNAME_IMPORT_NOTICE");
        if (importNotice == null) {
            importNotice = SysUtil.StaticUtil.README_SHEETNAME;
        }
        InputStream is = null;
        Workbook wb = null;
        Map<String, String> sheetNameMap = new LinkedHashMap<>();
        List<String> sheetNames = new ArrayList<>();
        List<String[]> sheetVal = null;
        try {
            if (isXlsx) {
                // sheetNames =
                // FileUtil.ExcelUtil.XLSXCovertCSVReader.readerSheetNamesByFile(excelFile);
                // if (sheetNames.contains(clsDefKey)) {
                // sheetVal =
                // FileUtil.ExcelUtil.XLSXCovertCSVReader.readerExcelByFile(excelFile,
                // clsDefKey, 0);
                // sheetVal.remove(0);
                // }
                sheetNames = EasyExcelUtil.getExcelAllSheetNames(excelFile);
                if (sheetNames.contains(clsDefKey)) {
                    EasyExcelUtil.SheetData sheetData = EasyExcelUtil.readSheet(clsDefKey, null, excelFile);
                    sheetVal = sheetData.getRows();
                }
            } else {
                is = file.getInputStream();
                wb = new HSSFWorkbook(is);
                for (int i = 0; i < wb.getNumberOfSheets(); i++) {
                    Sheet sheet = wb.getSheetAt(i);
                    String sheetName = sheet.getSheetName();
                    if (clsDefKey.equalsIgnoreCase(sheetName)) {
                        sheetVal = FileUtil.ExcelUtil.getSheetDatasArraybyPage(1, sheet.getLastRowNum(), sheet, null);
                    }
                    sheetNames.add(sheetName);
                }
            }
            if (!BinaryUtils.isEmpty(sheetVal)) {
                for (String[] vals : sheetVal) {
                    if (!BinaryUtils.isEmpty(vals[1]) && !sheetNameMap.containsKey(vals[1].toUpperCase())) {
                        sheetNameMap.put(vals[1].toUpperCase(), vals[1]);
                    }
                }
            }
            if (!BinaryUtils.isEmpty(sheetNames)) {
                sheetNames.forEach(name -> {
                    if (!sheetNameMap.containsKey(name.toUpperCase())) {
                        sheetNameMap.put(name.toUpperCase(), name);
                    }
                });
            }
        } catch (Exception e) {
            log.error("文件操作失败", e);
        } finally {
            try {
                is.close();
                wb.close();
            } catch (Exception e) {
            }
        }
        sheetNameMap.remove(importNotice);
        sheetNameMap.remove(clsDefKey);
        Assert.notEmpty(sheetNameMap.values(), "数据为空，不可导入！");
        message.setSheetNames(new ArrayList<>(sheetNameMap.values()));
        FileUtil.ExcelUtil.setImportExcelData(message, excelFile);
        return message;
    }

    @Override
    public ImportResultMessage importCiByClassBatch(Long domainId, CiExcelInfoDto excelInfoDto, boolean addAttr) {
        // 校验必要参数
        Assert.notNull(excelInfoDto.getFileName(), "X_PARAM_NOT_NULL${name:fileName}");
        Assert.notNull(excelInfoDto.getSheetNames(), "X_PARAM_NOT_NULL${name:sheetNames}");
        String clsDefKey = LanguageResolver.trans("BS_CI_CLASS_EXPORT_CLASS_DEFINITION");
        if (clsDefKey == null) {
            clsDefKey = "对象定义";
        }
        List<ImportSheetMessage> sheetMessages = new ArrayList<>();
        SysUser loginUser = SysUtil.getCurrentUserInfo();

        // 用于存储异步响应结果
        // saveMessage = new ArrayList<>();
        FileInputStream is = null;
        HSSFWorkbook workbook = null;
        // List<Long> saveClsIds = new ArrayList<>();
        // 读取本地文件
        rsmUtils.downloadRsmAndUpdateLocalRsm(excelInfoDto.getFileName());
        File file = new File(localPath + excelInfoDto.getFileName());
        List<String> sheetNames = excelInfoDto.getSheetNames().stream()/*.map(String::toUpperCase)*/.collect(Collectors.toList());
        // 判断excel文件版本，03和07版本读取方式不同
        String excelType = CommUtil.getImportExcelType(file.getName());
        boolean isXlsx = true;
        if (excelType.toUpperCase().equals(CommUtil.EXCEL07_XLSX_EXTENSION.toUpperCase())) {
            isXlsx = true;
        } else if (excelType.toUpperCase().equals(CommUtil.EXCEL03_XLS_EXTENSION.toUpperCase())) {
            isXlsx = false;
        } else {
            throw MessageException.i18n("BS_MNAME_NOT_SUPPORT_FILETYPE");
        }
        try {
            // 数据sheet集合
            List<String> fileSheetNames = new ArrayList<>();
            // 分类定义内容
            List<String[]> clsSheetVal = null;
            if (isXlsx) {
                // fileSheetNames =
                // FileUtil.ExcelUtil.XLSXCovertCSVReader.readerSheetNamesByFile(file);
                // if (fileSheetNames.contains(clsDefKey)) {
                // clsSheetVal =
                // FileUtil.ExcelUtil.XLSXCovertCSVReader.readerExcelByFile(file,
                // clsDefKey, 0);
                // clsSheetVal.remove(0);
                // }
                fileSheetNames = EasyExcelUtil.getExcelAllSheetNames(file);
                if (fileSheetNames.contains(clsDefKey)) {
                    EasyExcelUtil.SheetData sheetData = EasyExcelUtil.readSheet(clsDefKey, null, file);
                    clsSheetVal = sheetData.getRows();
                }
            } else {
                is = new FileInputStream(file);
                workbook = new HSSFWorkbook(is);
                for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
                    Sheet sheet = workbook.getSheetAt(i);
                    if (sheet.getSheetName().equalsIgnoreCase(clsDefKey)) {
                        clsSheetVal = FileUtil.ExcelUtil.getSheetDatasArraybyPage(1, sheet.getLastRowNum(), sheet, null);
                    }
                    fileSheetNames.add(sheet.getSheetName());
                }
            }

            //原有数据权限中不包含admin的分类权限
            Map<String, SysRoleDataModuleRlt> oldNoAdminRoleDataModuleRltMap = new HashMap<>();
            if (excelInfoDto.isOverwriteData()) {
                //清空数据
                BoolQueryBuilder ciClassQuery = QueryBuilders.boolQuery();
                ciClassQuery.must(QueryBuilders.termQuery("domainId", domainId));
                ciClassQuery.must(QueryBuilders.termsQuery("className.keyword", sheetNames));
                List<ESCIClassInfo> ciClassInfos = esClsSvc.getListByQuery(ciClassQuery);
                if (!ciClassInfos.isEmpty()) {
                    List<Long> existClassIdList = new ArrayList<>();
                    Map<Long, ESCIClassInfo> oldClassMap = new HashMap<>();
                    ciClassInfos.forEach(esciClassInfo -> {
                        existClassIdList.add(esciClassInfo.getId());
                        oldClassMap.put(esciClassInfo.getId(), esciClassInfo);
                    });
                    //获取子分类
                    List<ESCIClassInfo> childs = esClsSvc.getListByQuery(QueryBuilders.boolQuery().must(QueryBuilders.termsQuery("parentId", existClassIdList)));
                    childs.forEach(esciClassInfo -> {
                        existClassIdList.add(esciClassInfo.getId());
                        oldClassMap.put(esciClassInfo.getId(), esciClassInfo);
                    });
                    attrTransConfigSvc.deleteByQuery(QueryBuilders.termsQuery("classId", existClassIdList), true);
                    ciHistorySvc.deleteByQuery(QueryBuilders.termsQuery("classId", existClassIdList), true);
                    esClsSvc.deleteByIds(existClassIdList);
                    esCiSvc.deleteByQuery(QueryBuilders.termsQuery("classId", existClassIdList), true);
                    ciRltSvc.delRlts(null, existClassIdList, existClassIdList);

                    //删除分类授权
                    CSysRoleDataModuleRlt cdt = new CSysRoleDataModuleRlt();
                    String[] dataValues = new String[existClassIdList.size()];
                    for (int i = 0; i < existClassIdList.size(); i++) {
                        dataValues[i] = existClassIdList.get(i) + "";
                    }
                    cdt.setDataValues(dataValues);
                    cdt.setDomainId(domainId);
                    List<SysRoleDataModuleRlt> oldRoleDataModuleRlts = roleSvc.getRoleDataModuleRltByCdt(cdt);

                    //获取admin角色
                    BoolQueryBuilder roleQuery = QueryBuilders.boolQuery();
                    roleQuery.must(QueryBuilders.termQuery("domainId", domainId));
                    roleQuery.must(QueryBuilders.termQuery("roleName.keyword", "admin"));
                    List<SysRole> admin = roleSvc.getRolesByQuery(roleQuery);
                    if (admin.isEmpty()) {
                        throw new MessageException("缺少admin角色");
                    }
                    Long adminRoleId = admin.get(0).getId();
                    for (SysRoleDataModuleRlt oldRoleDataModuleRlt : oldRoleDataModuleRlts) {
                        if (!oldRoleDataModuleRlt.getRoleId().equals(adminRoleId)) {
                            Long oldClassId = Long.parseLong(oldRoleDataModuleRlt.getDataValue());
                            ESCIClassInfo esciClassInfo = oldClassMap.get(oldClassId);
                            oldNoAdminRoleDataModuleRltMap.put(esciClassInfo.getClassName(), oldRoleDataModuleRlt);
                        }
                    }
                    roleSvc.deleteRoleDataModuleRlt(cdt);
                }
            }

            // 先保存分类
            if (!BinaryUtils.isEmpty(clsSheetVal)) {
                ImportSheetMessage clsSheetMessage = clsSvc.importDirAndCIClass(domainId, clsSheetVal, sheetNames, addAttr);
                clsSheetMessage.setSheetName(clsDefKey);
                sheetMessages.add(clsSheetMessage);
            }
            // 查询分类数据
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            boolQuery.must(QueryBuilders.termsQuery("className.keyword", sheetNames));
            boolQuery.must(QueryBuilders.termQuery("domainId", domainId));
            List<ESCIClassInfo> ciClassInfos = esClsSvc.getListByQuery(boolQuery);
            Map<String, ESCIClassInfo> clsMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
            clsMap.putAll(BinaryUtils.toObjectMap(ciClassInfos, "className"));
            // 循环保存数据
            for (String sheetName : fileSheetNames) {
                if (sheetNames.contains(sheetName)) {
                    List<String> titles = new ArrayList<>();
                    List<String[]> sheetVal = null;
                    if (isXlsx) {
                        EasyExcelUtil.SheetData sheetData = EasyExcelUtil.readSheet(sheetName, null, file);
                        // sheetVal =
                        // FileUtil.ExcelUtil.XLSXCovertCSVReader.readerExcelByFile(file,
                        // sheetName, 0);
                        titles = sheetData.getTitles();
                        sheetVal = sheetData.getRows();
                        if (BinaryUtils.isEmpty(sheetVal)) {
                            continue;
                        }
                        // Collections.addAll(titles, sheetVal.remove(0));
                    } else {
                        if (workbook == null) {
                            is = new FileInputStream(file);
                            workbook = new HSSFWorkbook(is);
                        }
                        HSSFSheet sheet = workbook.getSheet(sheetName);
                        titles.addAll(FileUtil.ExcelUtil.getSheetTitles(sheet).values());
                        sheetVal = FileUtil.ExcelUtil.getSheetDatasArraybyPage(1, sheet.getLastRowNum(), sheet, null);
                        if (sheetVal == null) {
                            continue;
                        }
                    }
                    ImportSheetMessage sheetMessage = ImportSheetMessage.builder().sheetName(sheetName).className(sheetName).build();
                    sheetMessages.add(sheetMessage);
                    if (titles == null || titles.size() <= 1) {
                        sheetMessage.setTotalNum(sheetVal.size());
                        sheetMessage.setFailNum(sheetVal.size());
                        sheetMessage.setErrMsg(LanguageResolver.trans("BS_MNAME_CLASS_DATA_ERROR", "{\"field\":" + sheetName + "}"));
                        continue;
                    }
                    Map<String, List<String>> attrMark = FileUtil.ExcelUtil.getAttrMarkByTitles(titles);
                    titles = attrMark.get("titles");
                    // 填充读取的原始titles
                    sheetMessage.setTitles(titles);
                    if (!clsMap.containsKey(sheetName)) {
                        sheetMessage.setTotalNum(sheetVal.size());
                        sheetMessage.setFailNum(sheetVal.size());
                        sheetMessage.setErrMsg(LanguageResolver.trans("BS_MNAME_CLASS_NOT_EXSIT"));
                        continue;
                    }
                    ESCIClassInfo ciClassInfo = clsMap.get(sheetName);
                    // CcCiClassInfo ciClassInfo = ciClassInfos.get(0);
                    // saveClsIds.add(ciClassInfo.getId());
                    if (ciClassInfo.getParentId() != 0) {
                        List<CcCiAttrDef> allDefss = esClsSvc.getAllDefsByClassId(domainId, ciClassInfo.getId());
                        ciClassInfo.setCcAttrDefs(allDefss);
                    }
                    List<CcCiAttrDef> attrDefs = ciClassInfo.getCcAttrDefs();
                    // CI属性定义不存在！ 主要防止数据不正常现象
                    if (BinaryUtils.isEmpty(attrDefs)) {
                        sheetMessage.setTotalNum(sheetVal.size());
                        sheetMessage.setFailNum(sheetVal.size());
                        sheetMessage.setErrMsg(LanguageResolver.trans("BS_MNAME_NOT_CI_ATTR_DEF"));
                        continue;
                    }
                    Long classId = ciClassInfo.getId();
                    String className = ciClassInfo.getClassName();
                    int rIdx = 1; // 数据行号，用于标识错误信息
                    List<CcCiRecord> ciRecords = new ArrayList<CcCiRecord>();
                    for (String[] rows : sheetVal) {
                        // 取第一列的值
                        String oneCellValue = rows[0];
                        // 转成map
                        Map<String, String> map = new HashMap<String, String>();
                        for (int j = 1; j < titles.size(); j++) {
                            String val = rows[j] == null ? rows[j] : rows[j].trim();
                            map.put(titles.get(j), val);
                        }
                        rIdx++;
                        CcCiRecord record = new CcCiRecord();
                        record.setIndex(rIdx);
                        record.setAttrs(map);
                        // 有ciCode且值不为空则填充ciCode
                        if (!BinaryUtils.isEmpty(oneCellValue)) {
                            record.setCiCode(oneCellValue);
                        }
                        ciRecords.add(record);
                    }
                    ImportSheetMessage classDetailedResult = new ImportSheetMessage(className);
                    String ownerCode = BinaryUtils.isEmpty(excelInfoDto.getOwnerCode()) ? loginUser.getLoginCode() : excelInfoDto.getOwnerCode();
                    if (!ciRecords.isEmpty()) {
                        while (ciRecords.size() > 0) {
                            List<CcCiRecord> subList = ciRecords.subList(0, ciRecords.size() > 3000 ? 3000 : ciRecords.size());
                            CiClassSaveInfo saveInfo = new CiClassSaveInfo(classId, ciClassInfo.getClassStdCode(), null, ownerCode, subList, null);
                            classDetailedResult = this.saveOrUpdateCiBatchForExcelImport(domainId, saveInfo, ciClassInfo);
                            // 累计当前分类的结果后对当前分类的详情重新赋值
                            sheetMessage.setTotalNum(classDetailedResult.getTotalNum() + sheetMessage.getTotalNum());
                            sheetMessage.setSuccessNum(classDetailedResult.getSuccessNum() + sheetMessage.getSuccessNum());
                            sheetMessage.setFailNum(classDetailedResult.getFailNum() + sheetMessage.getFailNum());
                            sheetMessage.setIgnoreNum(classDetailedResult.getIgnoreNum() + sheetMessage.getIgnoreNum());
                            sheetMessage.setInsertNum(classDetailedResult.getInsertNum() + sheetMessage.getInsertNum());
                            sheetMessage.setUpdateNum(classDetailedResult.getUpdateNum() + sheetMessage.getUpdateNum());
                            if (!BinaryUtils.isEmpty(classDetailedResult.getRowMessages())) {
                                sheetMessage.getRowMessages().addAll(classDetailedResult.getRowMessages());
                            }
                            ciRecords.removeAll(subList);
                        }
                    }
                    // 等待每个分类落盘完成，防止不同分类间业务主键重复
                }
            }
            if(!CollectionUtils.isEmpty(ciClassInfos)){
                List<Long> classIds = ciClassInfos.stream().map(CcCiClass::getId).collect(Collectors.toList());
                classApiSvc.clearCacheEncode(classIds);
            }
        } catch (IllegalArgumentException e) {
            throw new MessageException(e.getMessage());
        } catch (MessageException e) {
            throw new MessageException(e.getMessage());
        } catch (Exception e) {
            throw new MessageException(e.getMessage());
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                }
            }
            if (workbook != null) {
                try {
                    workbook.close();
                } catch (IOException e) {
                }
            }
        }
        ImportResultMessage result = FileUtil.ExcelUtil.writeSheetMessageToFile(sheetMessages, "CI导入明细", clsDefKey);
        // 统计分类、CI导入详情
        for (ImportSheetMessage sheetMessage : sheetMessages) {
            if (clsDefKey.equalsIgnoreCase(sheetMessage.getSheetName())) {
                result.setDefTotalNum(sheetMessage.getTotalNum());
                result.setDefSuccessNum(sheetMessage.getSuccessNum());
                result.setDefFailNum(sheetMessage.getFailNum());
            }
        }
        file.delete();
        // 记录文件操作
        resourceSvc.saveSyncResourceInfo(excelInfoDto.getFileName(), urlPath + excelInfoDto.getFileName(), false, 1);
        // long time1 = System.currentTimeMillis();
        // long insertNum = 0;
        // for (ImportSheetMessage sheetMessage : sheetMessages) {
        // if (!clsDefKey.equals(sheetMessage.getSheetName())) {
        // insertNum += sheetMessage.getInsertNum();
        // }
        // }
        // long dbInsert = 0;
        // long errNum = 0;
        // while (insertNum - errNum > dbInsert) {
        // try {
        // Thread.sleep(2000);
        // } catch (InterruptedException e) {
        // }
        // dbInsert = 0;
        // Map<String, Long> clsCiCountMap =
        // esCiSvc.groupByCountField("classId", QueryBuilders.matchAllQuery());
        // for (Long clsId : saveClsIds) {
        // Long now = clsCiCountMap.get(String.valueOf(clsId)) == null ? 0L :
        // clsCiCountMap.get(String.valueOf(clsId));
        // Long old = dbCiCountMap.get(String.valueOf(clsId)) == null ? 0L :
        // dbCiCountMap.get(String.valueOf(clsId));
        // dbInsert += now - old;
        // }
        // errNum = 0;
        // for (Map<String, Object> saveMap : saveMessage) {
        // errNum += (Integer) saveMap.get("failCount") == null ? 0 : (Integer)
        // saveMap.get("failCount");
        // }
        // }
        // long time2 = System.currentTimeMillis();
        // log.info("wait for data save : " + (time2 - time1) + "ms");
        return result;
    }

    @Override
    public Integer deleteById(Long id, Long sourceId) {
        ciRltSvc.delRltByCiId(id);
        ESCIInfo esciInfo = esCiSvc.getById(id);
        CcCiInfo ciInfo = commSvc.tranCcCiInfo(esciInfo, true);
        // CcCiInfo ciInfo = getCiInfoById(id);
        // 记录CI操作日志
        this.saveCIOperateLog(sourceId, SysUtil.StaticUtil.LOG_DYNAMIC_DELETE, ciInfo.getAttrDefs(), ciInfo.getAttrs(), null, ciInfo.getCiClass().getClassName(), ciInfo.getCi());
        // 记录CI历史版本
        esCiSvc.transCIAttrs(Collections.singletonList(esciInfo), false);
//        esHistorySvc.saveOrUpdateHistoryInfo(esciInfo, ESCIHistoryInfo.ActionType.DELETE);
        return esCiSvc.deleteById(id);
    }

    @Override
    public Integer removeByIds(List<Long> ciIds, Long sourceId) {
        if (BinaryUtils.isEmpty(ciIds)) {
            return 1;
        }
        CCcCi cdt = new CCcCi();
        cdt.setIds(ciIds.toArray(new Long[]{}));
        // List<CcCiInfo> ciInfos = this.queryCiInfoPage(1L, 1, ciIds.size(),
        // cdt, null, true, true).getData();
        List<ESCIInfo> esciInfos = esCiSvc.getSortListByCdt(1, ciIds.size(), cdt, null, true).getData();
        List<CcCiInfo> ciInfos = commSvc.transEsInfoList(esciInfos, true);
        List<ESCIOperateLog> ciLogs = new ArrayList<>();
        ciInfos.forEach(ciInfo -> {
            ciLogs.add(buildLogRecord(sourceId, SysUtil.StaticUtil.LOG_DYNAMIC_DELETE, ciInfo.getAttrDefs(), ciInfo.getAttrs(), null, ciInfo.getCiClass().getClassName(), ciInfo.getCi()));
        });
        this.saveCIOperateLogBatch(ciLogs);
        esCiSvc.transCIAttrs(esciInfos, false);
//        esHistorySvc.saveOrUpdateHistoryInfosBatch(esciInfos, ESCIHistoryInfo.ActionType.DELETE);
        ciRltSvc.delRltByCiIds(ciIds);
        //删除生成的不合规数据
        try {
            iamsCIPrivateNonComplianceDao.deleteByIds(ciIds);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return esCiSvc.deleteByIds(ciIds);
    }

    @Override
    public Integer removeByPrimaryKeys(Long domainId, List<String> ciPrimaryKeys, Long sourceId) {
        Assert.isTrue(!BinaryUtils.isEmpty(ciPrimaryKeys), "X_PARAM_NOT_NULL${name:ciPrimaryKeys}");
        ESCISearchBean bean = new ESCISearchBean();
        bean.setCiPrimaryKeys(ciPrimaryKeys);
        bean.setPageNum(1);
        bean.setPageSize(ciPrimaryKeys.size());
        Page<ESCIInfo> page = searchESCIByBean(bean);
        Page<CcCiInfo> ciInfos = commSvc.transEsInfoPage(page, true);
        List<ESCIOperateLog> ciLogs = new ArrayList<>();
        List<Long> ciIds = new ArrayList<>();
        ciInfos.getData().forEach(ciInfo -> {
            ciIds.add(ciInfo.getCi().getId());
            ciLogs.add(buildLogRecord(sourceId, SysUtil.StaticUtil.LOG_DYNAMIC_DELETE, ciInfo.getAttrDefs(), ciInfo.getAttrs(), null, ciInfo.getCiClass().getClassName(), ciInfo.getCi()));
        });
        this.saveCIOperateLogBatch(ciLogs);
        esCiSvc.transCIAttrs(page.getData(), false);
//        esHistorySvc.saveOrUpdateHistoryInfosBatch(page.getData(), ESCIHistoryInfo.ActionType.DELETE);
        ciRltSvc.delRltByCiIds(ciIds);
        return esCiSvc.deleteByIds(ciIds);
    }

    @Override
    public Integer removeByClassId(Long classId, Long sourceId) {
        BoolQueryBuilder query = QueryBuilders.boolQuery();
        query.must(QueryBuilders.termQuery("classId", classId));
        long count = esCiSvc.countByCondition(query);
        if (count > 0) {
            long totalPages = count % 3000 == 0 ? count / 3000 : count / 3000 + 1;
            int pageNum = 0;
            while (pageNum < totalPages) {
                List<ESCIInfo> esciInfos = esCiSvc.getListByQuery(++pageNum, 3000, query).getData();
                List<CcCiInfo> ciInfos = commSvc.transEsInfoList(esciInfos, true);
                // List<CcCiInfo> ciInfos =
                // esCiSvc.getCIInfoPageByQuery(pageNum++, 3000, query,
                // true).getData();
                List<ESCIOperateLog> ciLogs = new ArrayList<>();
                ciInfos.forEach(ciInfo -> {
                    ciLogs.add(buildLogRecord(sourceId, SysUtil.StaticUtil.LOG_DYNAMIC_DELETE, ciInfo.getAttrDefs(), ciInfo.getAttrs(), null, ciInfo.getCiClass().getClassName(), ciInfo.getCi()));
                });
                this.saveCIOperateLogBatch(ciLogs);
                esCiSvc.transCIAttrs(esciInfos, false);
//                esHistorySvc.saveOrUpdateHistoryInfosBatch(esciInfos, ESCIHistoryInfo.ActionType.DELETE);
            }
            ciRltSvc.delRltByCiClassId(classId);
            esCiSvc.removeByClassId(classId);
        }
        return 1;
    }

    /**
     * 专为一键导入提供的批量保存接口
     *
     * @param saveInfo    要保存的CI列表
     * @param ciClassInfo 分类信息，子分类需要携带父级属性
     * @return
     */
    private ImportSheetMessage saveOrUpdateCiBatchForExcelImport(Long domainId, CiClassSaveInfo saveInfo, ESCIClassInfo ciClassInfo) {
        BinaryUtils.checkEmpty(saveInfo, "saveInfo");
        BinaryUtils.checkEmpty(ciClassInfo, "classInfo");
        BinaryUtils.checkEmpty(ciClassInfo.getId(), "classId");
        BinaryUtils.checkEmpty(ciClassInfo.getCcAttrDefs(), "attrDefs");
        BinaryUtils.checkEmpty(saveInfo.getOwnerCode(), "ownerCode");
        BinaryUtils.checkEmpty(saveInfo.getClassStdCode(), "classStdCode");
        ImportSheetMessage detailedResult = ImportSheetMessage.builder().className(ciClassInfo.getClassName()).build();
        // 待保存数据为空，直接返回
        List<CcCiRecord> records = saveInfo.getRecords();
        if (BinaryUtils.isEmpty(records)) {
            return detailedResult;
        }
        SysUser loginUser = SysUtil.getCurrentUserInfo();
        Long sourceId = saveInfo.getSourceId() == null ? 1L : saveInfo.getSourceId();
        String ownerCode = saveInfo.getOwnerCode();
        Long classId = saveInfo.getClassId() == null ? ciClassInfo.getId() : saveInfo.getClassId();
        List<ImportRowMessage> rowMessages = new ArrayList<>();
        // 统计信息
        int successCount = 0;
        int failCount = 0;
        int totals = 0;
        int ignores = 0;
        int inserts = 0;
        int updates = 0;
        // 保存CI
        if (!BinaryUtils.isEmpty(records)) {
            //修改了属性值但非创建者
            Map<String, CheckAssertEditRes> unEditAuth = checkUnEditAuth(records, saveInfo, ciClassInfo);
            totals = records.size();
            List<CcCiAttrDef> attrDefs = ciClassInfo.getCcAttrDefs();

            List<String> defNames = attrDefs.stream().map(CcCiAttrDef::getProStdName).collect(Collectors.toList());
            // 查询要更新的数据
            Map<String, CcCiInfo> ciCodeMap = new HashMap<>();
            Set<String> ciCodeSet = records.stream().map(CcCiRecord::getCiCode).filter(code -> !BinaryUtils.isEmpty(code)).collect(Collectors.toSet());
            if (!BinaryUtils.isEmpty(ciCodeSet)) {
                BoolQueryBuilder query = QueryBuilders.boolQuery();
                query.must(QueryBuilders.termsQuery("ciCode.keyword", ciCodeSet));
                query.must(QueryBuilders.termQuery(OWNER_CODE_KEYWORD, ownerCode));
                List<CcCiInfo> esInfos = esCiSvc.getCIInfoPageByQuery(1, 5000, query, false).getData();
                if (!BinaryUtils.isEmpty(esInfos)) {
                    for (CcCiInfo esCiInfo : esInfos) {
                        ciCodeMap.put(esCiInfo.getCi().getCiCode(), esCiInfo);
                    }
                }
            }
            ESCIClassInfo classInfo = classApiSvc.queryClassById(classId);
            List<CcCiInfo> ciInfosList = new ArrayList<CcCiInfo>();
            List<Integer> hashCodes = new ArrayList<>();
            Map<String, Integer> keyToIndex = new HashMap<>();
            List<String> saveCodes = new ArrayList<>();
            List<String> savePrimaryKeys = new ArrayList<>();

            // 获取所属资产配置
            List<CcCiAttrDef> releAssetAttrDefs = attrDefs.stream().filter(e -> e.getProType()
                    .equals(AttrNameKeyEnum.EXTERNAL_ASSET.getType())).collect(Collectors.toList());
            List<Long> releClassIdList = new ArrayList<>();
            Map<String, Long> attrMappingClassId = new HashMap<>();
            for (CcCiAttrDef releAssetAttrDef : releAssetAttrDefs) {
                String proDropSourceDef = releAssetAttrDef.getProDropSourceDef();
                if (!BinaryUtils.isEmpty(proDropSourceDef)) {
                    releClassIdList.add(Long.valueOf(proDropSourceDef));
                    attrMappingClassId.put(releAssetAttrDef.getProStdName(), Long.valueOf(proDropSourceDef));
                } else {
                    throw new BinaryException("所属资产【" + releAssetAttrDef.getProName() + "】配置异常,不可为空");
                }
            }
            Map<Long, Map<String, ESCIInfo>> classIdGroupCiMap = getClassCiGroup(releClassIdList);

            // CI校验-属性
            // 获取数据字典值
            Map<String, List<String>> dictValuesMap = this.getExterDictValues(domainId, attrDefs);
            Iterator<CcCiRecord> it = records.iterator();
            Map<String, ESCIOperateLog> updateLogMap = new HashMap<>();
            recordLoop:
            while (it.hasNext()) {
                boolean isUpdate = false;
                CcCiRecord record = it.next();
                Map<String, String> attrs = record.getAttrs();
                // 校验属性
                Map<String, Integer> errMsg = commSvc.validAttrs(attrDefs, attrs, true);
                getEnCodeNumByDef(classInfo==null?saveInfo.getClassStdCode():classInfo.getClassCode(), attrDefs, attrs);
                if (!BinaryUtils.isEmpty(errMsg)) {
                    List<ImportCellMessage> cellMessages = new ArrayList<>();
                    errMsg.forEach((msg, errType) -> {
                        ImportCellMessage cellMessage = new ImportCellMessage();
                        cellMessage.setErrorType(errType);
                        cellMessage.setErrorDesc(msg);
                        cellMessages.add(cellMessage);
                    });
                    ImportRowMessage rowMessage = new ImportRowMessage();
                    rowMessage.setRowNum(record.getIndex());
                    rowMessage.setMessageItems(cellMessages);
                    rowMessages.add(rowMessage);
                    failCount++;
                    it.remove();
                    continue;
                }
                Map<String, String> stdMap = toStdMap(attrs);
                // 校验转换导入关联资产类型
                boolean checkAssetFlag = false;
                if (!CollectionUtils.isEmpty(classIdGroupCiMap)) {
                    for (Entry<String, String> attrEntry : stdMap.entrySet()) {
                        String key = attrEntry.getKey();
                        String value = attrEntry.getValue();
                        if (attrMappingClassId.containsKey(key) && !BinaryUtils.isEmpty(value)) {
                            Long releClassId = attrMappingClassId.get(key);
                            List<JSONObject> valueList = new ArrayList<>();
                            Map<String, ESCIInfo> classCiCodeMap = classIdGroupCiMap.get(releClassId);
                            if (CollectionUtils.isEmpty(classCiCodeMap)) {
                                rowMessages.add(this.buildRowMessage(record.getIndex(), CheckAttrUtil.FAILURE, "关联属性[" + key + "]引用值[" + releClassId + "]不存在"));
                                ignores++;
                                it.remove();
                                continue recordLoop;
                            }
                            ESCIInfo esciInfo = null;
                            JSONObject val = JSONObject.parseObject(value);
                            String ciCode = val.getString("ciCode");
                            if (org.apache.commons.lang.StringUtils.isNotBlank(ciCode)) {
                                esciInfo = classCiCodeMap.get(ciCode);
                            }

                            if (null != esciInfo) {
                                valueList.add(val);
                            } else {
                                // 不存在
                                checkAssetFlag = true;
                            }
                            if (!CollectionUtils.isEmpty(valueList)) {
                                stdMap.put(key, JSONObject.toJSONString(valueList));
                            }
                        }
                        if (checkAssetFlag) {
                            rowMessages.add(this.buildRowMessage(record.getIndex(), CheckAttrUtil.FAILURE, "关联属性[" + key + "]引用值[" + value + "]不存在"));
                            ignores++;
                            it.remove();
                            continue recordLoop;
                        }
                    }
                }
                // 校验数据字典类型
                if (!BinaryUtils.isEmpty(dictValuesMap)) {
                    Iterator<Entry<String, List<String>>> dictIt = dictValuesMap.entrySet().iterator();
                    while (dictIt.hasNext()) {
                        Entry<String, List<String>> next = dictIt.next();
                        String key = next.getKey();
                        List<String> values = next.getValue();
                        String val = stdMap.get(key);
                        if (record.getCiId() == null && !BinaryUtils.isEmpty(val) && !values.contains(val)) {
                            rowMessages.add(this.buildRowMessage(record.getIndex(), CheckAttrUtil.FAILURE, "属性[" + key + "]引用值[" + val + "]不存在"));
                            ignores++;
                            it.remove();
                            continue recordLoop;
                        }
                    }
                }
                // 校验ciCode，同一分类下，ciCode相同更新
                String ciCode = record.getCiCode();
                if (!BinaryUtils.isEmpty(ciCode)) {
                    if (saveCodes.contains(ciCode)) {
                        rowMessages.add(this.buildRowMessage(record.getIndex(), CheckAttrUtil.EXIST, "ciCode【" + ciCode + "】重复"));
                        ignores++;
                        it.remove();
                        continue;
                    } else if (ciCodeMap.containsKey(ciCode)) {
                        CcCiInfo esciInfo = ciCodeMap.get(ciCode);
                        // 不同分类下ciCode相同，视为重复，同分类下则更新
                        if (esciInfo.getCi().getClassId().longValue() == classId.longValue()) {
                            record.setCiId(esciInfo.getCi().getId());
                            updates++;
                            isUpdate = true;
                        } else {
                            rowMessages.add(this.buildRowMessage(record.getIndex(), CheckAttrUtil.EXIST, "ciCode【" + ciCode + "】重复"));
                            ignores++;
                            it.remove();
                            continue;
                        }
                    }
                    saveCodes.add(ciCode);
                }
                List<String> ciPKAttrDefNames = CommUtil.getCiPKAttrDefNames(attrDefs);
                // 获取所属分类下的业务主键值的hashCode
                Integer hashCode;
                try {
                    hashCode = CommUtil.getCiMajorHashCode(attrs, ciPKAttrDefNames, saveInfo.getClassStdCode(), saveInfo.getOwnerCode());
                } catch (Exception e) {
                    failCount++;
                    rowMessages.add(this.buildRowMessage(record.getIndex(), CheckAttrUtil.FAILURE, "业务主键生成失败"));
                    continue;
                }
                // 获取当前CI的业务主键值
                List<String> ciPrimaryKeys = CommUtil.getCiPrimaryKeys(saveInfo.getClassStdCode(), attrs, ciPKAttrDefNames);
                String primaryKey = JSON.toString(ciPrimaryKeys);
                if (savePrimaryKeys.contains(primaryKey)) {
                    ignores++;
                    rowMessages.add(this.buildRowMessage(record.getIndex(), CheckAttrUtil.EXIST, "业务主键【" + ciPrimaryKeys + "】重复"));
                    continue;
                } else {
                    savePrimaryKeys.add(primaryKey);
                }
                //校验权限，存在导入数据为非当前人创建且设计库存在同主键或同ciCode数据，当前数据不能导入
                String checkCiCode = StringUtils.isBlank(ciCode) ? "empty" : ciCode;
                if (unEditAuth.containsKey(checkCiCode) || unEditAuth.containsKey(primaryKey)) {
                    CheckAssertEditRes res = unEditAuth.getOrDefault(checkCiCode, unEditAuth.get(primaryKey));
                    failCount++;
                    if (isUpdate) {
                        updates--;
                    }
                    String msg = "该资产为引用资产，若需变更，请联系资产创建人-" + res.getCreatorName() + "于“架构应用-资产管理”页面进行变更";
                    rowMessages.add(this.buildRowMessage(record.getIndex(), CheckAttrUtil.FAILURE, msg));
                    continue;
                }
                CcCiInfo ciInfo = new CcCiInfo();
                // 属性过滤，只保存定义过的属性
                Iterator<String> itAttr = stdMap.keySet().iterator();
                while (itAttr.hasNext()) {
                    String attrName = itAttr.next();
                    // 属性不包含在定义的属性，并且不是下划线开头结尾的字段，去掉
                    if (!defNames.contains(attrName) && !attrName.matches("_([\\d\\D]*?)_")) {
                        itAttr.remove();
                    }
                }
                // CI 属性大小写转换
                ciInfo.setAttrs(stdMap);
                CcCi ci = new CcCi();
                if (isUpdate) {
                    CcCiInfo oldCi = ciCodeMap.get(ciCode);
                    ci.setCreateTime(oldCi.getCi().getCreateTime());
                    ci.setCiVersion(oldCi.getCi().getCiVersion());
                    ci.setLocalVersion(oldCi.getCi().getLocalVersion());
                    ci.setPublicVersion(oldCi.getCi().getPublicVersion());
                    // 属性合并
                    Map<String, String> combineAttrs = new HashMap<>();
                    combineAttrs.putAll(oldCi.getAttrs());
                    combineAttrs.putAll(stdMap);
                    ciInfo.setAttrs(combineAttrs);
                    if (!CheckAttrUtil.checkAttrMapEqual(combineAttrs, oldCi.getAttrs())) {
                        ci.setCiVersion(String.valueOf(Long.parseLong(oldCi.getCi().getCiVersion()) + 1L));
                        ci.setLocalVersion(oldCi.getCi().getLocalVersion() + 1L);
                    }
                    if (StringUtils.isBlank(oldCi.getCi().getOwnerCode())) {
                        ci.setOwnerCode(loginUser == null ? "system" : loginUser.getLoginCode());
                    }
                    if (StringUtils.isBlank(oldCi.getCi().getCreator())) {
                        ci.setCreator(loginUser == null ? "system" : loginUser.getLoginCode());
                    }
                } else {
                    ci.setCiVersion(String.valueOf(1));
                    ci.setCreator(loginUser == null ? "system" : loginUser.getLoginCode());
                    ci.setOwnerCode(loginUser == null ? "system" : loginUser.getLoginCode());
                    ci.setCreateTime(ESUtil.getNumberDateTime());
                    ci.setLocalVersion(1L);
                }
                ci.setId(record.getCiId() == null ? ESUtil.getUUID() : record.getCiId());
                ci.setCiCode(record.getCiCode());
                ci.setClassId(classId);
                ci.setSourceId(sourceId);
                ci.setOwnerCode(ownerCode);
                ci.setDataStatus(1);
                ci.setHashCode(hashCode);
                ci.setCiPrimaryKey(primaryKey);
                ci.setCreateTime(ESUtil.getNumberDateTime());
                ci.setDomainId(1L);
                ci.setPublicVersion(0L);
                ci.setCiVersion("0");
                ciInfo.setCi(ci);
                ciInfosList.add(ciInfo);
                hashCodes.add(hashCode);
                keyToIndex.put(ci.getCiPrimaryKey(), record.getIndex());
                if (isUpdate) {
                    ESCIOperateLog ciLog = buildLogRecord(sourceId, SysUtil.StaticUtil.LOG_DYNAMIC_UPDATE, attrDefs, ciCodeMap.get(ciCode).getAttrs(), stdMap, ciClassInfo.getClassName(), ci);
                    updateLogMap.put(ciCode, ciLog);
                }
            }
            List<String> repeatKeys = new ArrayList<>();
            // 校验hashCode是否重复
            Map<String, Integer> res = this.checkRecordsByHashCode(ciInfosList, repeatKeys, updateLogMap);
            ignores += res.get("ignore") == null ? 0 : res.get("ignore");
            updates += res.get("update") == null ? 0 : res.get("update");
            if (!BinaryUtils.isEmpty(repeatKeys)) {
                for (String str : repeatKeys) {
                    rowMessages.add(this.buildRowMessage(keyToIndex.get(str), CheckAttrUtil.EXIST, "业务主键【" + str + "】重复"));
                }
            }
            // 保存CI操作日志
            List<ESCIOperateLog> ciLogs = new ArrayList<ESCIOperateLog>(updateLogMap.values());
            for (ESCIOperateLog ciLog : ciLogs) {
                ciLog.setSourceId(sourceId);
                ciLog.setCiClassName(ciClassInfo.getClassName());
                ciLog.setProNames(attrDefs.stream().map(CcCiAttrDef::getProName).collect(Collectors.toList()));
                ciLog.setOperator(loginUser == null ? "system" : loginUser.getLoginCode());
            }
            Set<String> keySet = updateLogMap.keySet();
            if (ciInfosList.size() > keySet.size()) {
                ciInfosList.forEach(ciInfo -> {
                    if (BinaryUtils.isEmpty(ciInfo.getCi().getCiCode())) {
                        ciInfo.getCi().setCiCode(String.valueOf(ciInfo.getCi().getId()));
                    }
                    String ciCode = ciInfo.getCi().getCiCode();
                    if (!keySet.contains(ciCode)) {
                        ciLogs.add(buildLogRecord(sourceId, SysUtil.StaticUtil.LOG_DYNAMIC_INSERT, attrDefs, null, ciInfo.getAttrs(), ciClassInfo.getClassName(), ciInfo.getCi()));
                    }
                });
            }
            Map<String, Object> saveOrUpdateMsg = esCiSvc.saveOrUpdateCIBatch(ciInfosList, false);
            // if (!BinaryUtils.isEmpty(saveOrUpdateMsg.get("failCount"))) {
            // failCount += (Integer) saveOrUpdateMsg.get("failCount");
            // }
            inserts = (totals - failCount - updates - ignores);
            successCount = inserts + updates;
            if (!BinaryUtils.isEmpty(saveOrUpdateMsg.get("errMessge"))) {
                String errStr = JSON.toString(saveOrUpdateMsg.get("errMessge"));
                // 入库失败
                rowMessages.add(this.buildRowMessage(1, CheckAttrUtil.FAILURE, "入库失败：" + errStr));
            }
            this.saveCIOperateLogBatch(ciLogs);
            // saveMessage.add(saveOrUpdateMsg);
        }
        // 汇总整理结果
        detailedResult.setSuccessNum(successCount);
        detailedResult.setFailNum(failCount);
        detailedResult.setInsertNum(inserts);
        detailedResult.setUpdateNum(updates);
        detailedResult.setIgnoreNum(ignores);
        detailedResult.setTotalNum(totals);
        detailedResult.setRowMessages(rowMessages);
        return detailedResult;
    }

    private Map<String, CheckAssertEditRes> checkUnEditAuth(List<CcCiRecord> records, CiClassSaveInfo saveInfo, ESCIClassInfo ciClassInfo) {
        List<CcCiAttrDef> attrDefs = ciClassInfo.getCcAttrDefs();
        List<String> ciPKAttrDefNames = CommUtil.getCiPKAttrDefNames(attrDefs);

        String curLoginCode = SysUtil.getCurrentUserInfo().getLoginCode();
        List<CheckAssertEditCiParam> ciParams = new ArrayList<>();
        for (CcCiRecord record : records) {
            CheckAssertEditCiParam param = new CheckAssertEditCiParam();
            param.setCiCode(StringUtils.isBlank(record.getCiCode()) ? "empty" : record.getCiCode());
            List<String> ciPrimaryKeys = CommUtil.getCiPrimaryKeys(saveInfo.getClassStdCode(), record.getAttrs(), ciPKAttrDefNames);
            String primaryKey = JSON.toString(ciPrimaryKeys);
            param.setCiPrimaryKey(primaryKey);
            param.setLibType(LibType.PRIVATE);
            ciParams.add(param);
        }
        CheckAssertEditReq req = new CheckAssertEditReq();
        req.setFromDiagram(true);
        req.setCiParams(ciParams);
        req.setOperator(curLoginCode);
        List<CheckAssertEditRes> checkList = bmDiagramSvc.checkAssertEditAuth(req, true);
        Map<String, CheckAssertEditRes> checkMap = new ConcurrentHashMap<>();
        for (CheckAssertEditRes res : checkList) {
            if (!"empty".equals(res.getCiCode())) {
                checkMap.put(res.getCiCode(), res);
            }
            checkMap.put(res.getCiPrimaryKey(), res);
        }

        Map<String, CheckAssertEditRes> unEditAuth = new ConcurrentHashMap<>();
        for (CcCiRecord record : records) {
            String ciCode = StringUtils.isBlank(record.getCiCode()) ? "empty" : record.getCiCode();
            List<String> ciPrimaryKeys = CommUtil.getCiPrimaryKeys(saveInfo.getClassStdCode(), record.getAttrs(), ciPKAttrDefNames);
            String primaryKey = JSON.toString(ciPrimaryKeys);
            CheckAssertEditRes checkRes = getCheckRes(ciCode, primaryKey, checkMap);
            if (checkRes == null || checkRes.getEdit() || checkRes.getSameKeyDesignCiInfo() == null) {
                continue;
            }
            unEditAuth.put(ciCode, checkRes);
            unEditAuth.put(primaryKey, checkRes);
        }
        return unEditAuth;
    }

    private CheckAssertEditRes getCheckRes(String ciCode, String primaryKey, Map<String, CheckAssertEditRes> checkMap) {
        CheckAssertEditRes res = checkMap.get(ciCode);
        if (res == null) {
            res = checkMap.get(primaryKey);
        }
        return res;
    }

    private Map<Long, Map<String, ESCIInfo>> getClassCiGroup(List<Long> releClassIdList) {
        Map<Long, Map<String, ESCIInfo>> classIdGroupCiMap = new HashMap<>();
        if (!CollectionUtils.isEmpty(releClassIdList)) {
            List<ESCIInfo> releCIList = new ArrayList<>();
            ESCISearchBean bean = new ESCISearchBean();
            bean.setClassIds(releClassIdList);
            bean.setPageSize(10000);
            Page<ESCIInfo> esciInfoPage = esCiSvc.searchESCIByBean(bean);
            releCIList.addAll(esciInfoPage.getData());
            for (int i = 2; i < esciInfoPage.getTotalPages() + 1; i++) {
                bean.setPageNum(i);
                Page<ESCIInfo> ciByBean = esCiSvc.searchESCIByBean(bean);
                releCIList.addAll(ciByBean.getData());
                if (CollectionUtils.isEmpty(ciByBean.getData())) {
                    break;
                }
            }
            for (ESCIInfo esciInfo : releCIList) {
                Map<String, ESCIInfo> classCodekeyMap = classIdGroupCiMap.computeIfAbsent(esciInfo.getClassId(), k -> new HashMap<String, ESCIInfo>());
                classCodekeyMap.put(esciInfo.getCiCode(), esciInfo);
            }
        }
        return classIdGroupCiMap;
    }

    @SuppressWarnings("unchecked")
    public Map<String, List<String>> getExterDictValues(Long domainId, List<CcCiAttrDef> attrDefs) {
        Assert.isTrue(!BinaryUtils.isEmpty(attrDefs), "X_PARAM_NOT_NULL${name:attrDefs}");
        Map<String, List<String>> dictValMap = new HashMap<>();
        List<CcCiAttrDef> dictDefs =
                attrDefs.stream().filter(def -> def.getProType() == AttrNameKeyEnum.DICT.getType() && !BinaryUtils.isEmpty(def.getProDropSourceDef())).collect(Collectors.toList());
        for (CcCiAttrDef def : dictDefs) {
            try {
                Long dictClassId = CiClassProDropSourceDefHelper.getCiClassProDropSourceClassId(def.getProDropSourceDef().trim());
                Long[] dictDefIds = CiClassProDropSourceDefHelper.getCiClassProDropSourceDefIds(def.getProDropSourceDef().trim());
                List<String> dictValues = dictSvc.getExteralDictValues(domainId, dictClassId, dictDefIds);
                dictValMap.put(def.getProName().toUpperCase(), dictValues);
            } catch (Exception e) {
                Assert.isTrue(false, "属性[" + def.getProName().toUpperCase() + "]引用不合法");
            }
        }
        return dictValMap;
    }

    /**
     * icon path conversion
     *
     * @param attrDefs attributes def
     * @param attrs    ci attributes
     */
    private void removeModelIconPath(List<CcCiAttrDef> attrDefs, Map<String, String> attrs) {
        if (isShow3dAttribute) {
            String getProStdName = this.getModelProStdName(attrDefs);
            if (!"".equals(getProStdName)) {
                this.convertModelPath(attrs, getProStdName, false);
            }
        }
    }

    /**
     * Get the attribute name corresponding to the 3D model
     *
     * @param bean        ESCISearchBean
     * @param ciGroupPage ciGroupPage
     */
    private void addModelIconPath(ESCISearchBean bean, CiGroupPage ciGroupPage) {
        if (isShow3dAttribute) {
            long classId = bean.getCdt().getClassId();
            ESCIClassInfo esciClassInfo = esClsSvc.getById(classId);
            String modelAttrDef = this.getModelProStdName(esciClassInfo.getCcAttrDefs());

            List<CcCiInfo> ccCiInfos = ciGroupPage.getData();
            if (!"".equals(modelAttrDef)) {
                ccCiInfos.forEach(ciInfo -> {
                    Map<String, String> dbCiAttrs = ciInfo.getAttrs();
                    if (dbCiAttrs != null && !dbCiAttrs.isEmpty()) {
                        this.convertModelPath(dbCiAttrs, modelAttrDef, true);
                    }
                });
            }
        }
    }


    /**
     * Get the attribute name corresponding to the 3D model
     *
     * @param attrDefs CcCiAttrDef
     */
    private String getModelProStdName(List<CcCiAttrDef> attrDefs) {
        String modelAttrDef = "";
        List<String> modelAttrDefNames = new ArrayList<>(1);

        attrDefs.forEach(attr -> {
            int attrType = attr.getProType();
            if (attrType == AttrNameKeyEnum.MODEL.getType()) {
                modelAttrDefNames.add(attr.getProStdName());
            }
        });
        if (modelAttrDefNames.size() > 0) {
            modelAttrDef = modelAttrDefNames.get(0);
        }
        return modelAttrDef;
    }


    /**
     * Get the attribute name corresponding to the 3D model
     *
     * @param attrs      attributes definition
     * @param proStdName 3D model attribute name
     * @param isAddPath  add url path / remove url path
     */
    private void convertModelPath(Map<String, String> attrs, String proStdName, Boolean isAddPath) {
        String modelVal = attrs.get(proStdName);
        if (modelVal != null) {
            if (modelVal.startsWith(this.urlPath)) {
                modelVal = modelVal.replaceAll(this.urlPath, "");
            }
            if (isAddPath) {
                modelVal = this.urlPath + modelVal;
            }
            attrs.put(proStdName, modelVal);
        }
    }


    /**
     * When one-click import, check the path of the image containing the 3D model
     *
     * @param _3DModelAttrName 3D type attribute name
     * @param attrs            With check attribute collection
     * @param imagesPath       Existing image path
     */
    private void _3DmodelAttributeImgPathVerification(String _3DModelAttrName, Map<String, String> attrs, Set<String> imagesPath) {
        String iconPath = attrs.get(_3DModelAttrName);
        if (iconPath != null) {
            if (!imagesPath.contains(iconPath)) {
                String defaultPath = esClsSvc.getDefault3dModel();
                attrs.put(_3DModelAttrName, defaultPath);
            }
        }
    }


    /**
     * When exporting CI, check and remove the path
     *
     * @param attrDefs attrDefs
     * @param attrs    With check attribute collection
     */
    private void ciExport3DmodelAttrPathCheck(List<CcCiAttrDef> attrDefs, Map<String, Object> attrs) {
        if (isShow3dAttribute) {
            String _3DModelAttributeDefinition = this.getModelProStdName(attrDefs);
            if (null != _3DModelAttributeDefinition && !"".equals(_3DModelAttributeDefinition)) {
                Object _3DAttrValue = attrs.get(_3DModelAttributeDefinition);
                if (null != _3DAttrValue) {
                    String _3DAttrValueStr = _3DAttrValue.toString();
                    if (_3DAttrValueStr.startsWith(this.urlPath)) {
                        _3DAttrValueStr = _3DAttrValueStr.replaceAll(this.urlPath, "");
                    }
                    attrs.put(_3DModelAttributeDefinition, _3DAttrValueStr);
                }
            }
        }
    }

    /**
     * KEY转换成大写
     *
     * @param map
     * @return
     */
    public Map<String, String> toStdMap(Map<String, String> map) {
        Map<String, String> ret = new HashMap<String, String>();
        if (map == null) {
            return ret;
        }
        Set<String> keySet = map.keySet();
        for (String key : keySet) {
            if (key != null) {
                ret.put(key.trim().toUpperCase(), map.get(key));
            } else {
                ret.put(null, map.get(key));
            }
        }
        return ret;
    }

    private ImportRowMessage buildRowMessage(Integer rowNum, Integer errorType, String message) {
        ImportRowMessage rowMessage = new ImportRowMessage();
        rowMessage.setRowNum(rowNum);
        ImportCellMessage cellMessage = new ImportCellMessage();
        cellMessage.setErrorType(errorType);
        cellMessage.setErrorDesc(message);
        rowMessage.setMessageItems(Collections.singletonList(cellMessage));
        return rowMessage;
    }

    private boolean compareCiPrimaryKeys(List<String> ciPks1, List<String> ciPks2) {
        try {
            return CommUtil.compareToCiPks(ciPks1, ciPks2);
        } catch (Exception e) {
            log.error("do compareToCiPks 【" + JSON.toString(ciPks1) + "】 and 【 " + JSON.toString(ciPks2) + "】 err!");
        }
        return false;
    }

    protected Map<String, Integer> checkRecordsByHashCode(List<CcCiInfo> ciInfosList, List<String> repeatKeys, Map<String, ESCIOperateLog> updateLogMap) {
        if (BinaryUtils.isEmpty(ciInfosList)) {
            return new HashMap<>();
        }
        // updateLogMap用于判断根据ciCode更新的CI，避免更新操作重复记录
        Map<String, Integer> res = new HashMap<>();
        int ignore = 0;
        int update = 0;
        Set<Integer> hashCodes = new HashSet<>();
        ciInfosList.forEach(ciInfo -> hashCodes.add(ciInfo.getCi().getHashCode()));
        final String ownerCode = ciInfosList.get(0).getCi().getOwnerCode();
        BoolQueryBuilder hashCodeQuery = QueryBuilders.boolQuery();
        hashCodeQuery.must(QueryBuilders.termsQuery("hashCode", hashCodes));
        hashCodeQuery.must(QueryBuilders.termQuery(OWNER_CODE_KEYWORD, ownerCode));
        long liveCisCount = esCiSvc.countByCondition(hashCodeQuery);
        if (liveCisCount <= 0) {
            return res;
        }
        List<CcCiInfo> liveCis = esCiSvc.getCIInfoPageByQuery(1, new BigDecimal(liveCisCount).intValue(), hashCodeQuery, false).getData();
        Map<Integer, List<CcCiInfo>> dbCIs = new HashMap<>();
        liveCis.forEach(ciInfo -> {
            CcCi ci = ciInfo.getCi();
            if (dbCIs.get(ci.getHashCode()) == null) {
                dbCIs.put(ci.getHashCode(), new LinkedList<>());
            }
            dbCIs.get(ci.getHashCode()).add(ciInfo);
        });
        Iterator<CcCiInfo> iterator = ciInfosList.iterator();
        while (iterator.hasNext()) {
            CcCiInfo ciInfo = iterator.next();
            // 校验是否与库中的CI重复
            if (dbCIs.containsKey(ciInfo.getCi().getHashCode())) {
                List<CcCiInfo> esInfos = dbCIs.get(ciInfo.getCi().getHashCode());
                for (CcCiInfo esInfo : esInfos) {
                    CcCi ci = esInfo.getCi();
                    if (compareCiPrimaryKeys(JSON.toList(ciInfo.getCi().getCiPrimaryKey(), String.class), JSON.toList(ci.getCiPrimaryKey(), String.class)) && ciInfo.getCi().getOwnerCode().equals(ci.getOwnerCode())) {
                        // 同分类下业务主键重复，更新
                        boolean isUpdate = ci.getClassId().longValue() == ciInfo.getCi().getClassId().longValue()
                                && (BinaryUtils.isEmpty(ciInfo.getCi().getCiCode()) || ci.getCiCode().equals(ciInfo.getCi().getCiCode())) && ciInfo.getCi().getOwnerCode().equals(ci.getOwnerCode());
                        if (isUpdate) {
                            if (!updateLogMap.containsKey(ci.getCiCode())) {
                                update++;
                                ESCIOperateLog ciLog = ESCIOperateLog.builder().ciId(ci.getId()).ciCode(ci.getCiCode()).ciPrimaryKey(ciInfo.getCi().getCiPrimaryKey())
                                        .dynamic(SysUtil.StaticUtil.LOG_DYNAMIC_UPDATE).newAttrs(ciInfo.getAttrs()).oldAttrs(esInfo.getAttrs()).libaryId(1).build();
                                updateLogMap.put(ci.getCiCode(), ciLog);
                            }
                            ciInfo.getCi().setCiVersion(esInfo.getCi().getCiVersion());
                            ciInfo.getCi().setLocalVersion(esInfo.getCi().getLocalVersion());
                            ciInfo.getCi().setPublicVersion(esInfo.getCi().getPublicVersion());
                            // 属性合并
                            Map<String, String> combineAttrs = new HashMap<>();
                            combineAttrs.putAll(esInfo.getAttrs());
                            combineAttrs.putAll(ciInfo.getAttrs());
                            ciInfo.setAttrs(combineAttrs);
                            if (!CheckAttrUtil.checkAttrMapEqual(combineAttrs, esInfo.getAttrs())) {
                                ciInfo.getCi().setCiVersion(String.valueOf(Long.parseLong(esInfo.getCi().getCiVersion()) + 1L));
                                ciInfo.getCi().setLocalVersion(esInfo.getCi().getLocalVersion() + 1L);
                            }
                            ciInfo.getCi().setId(ci.getId());
                            ciInfo.getCi().setCiCode(ci.getCiCode());
                            ciInfo.getCi().setCreateTime(ci.getCreateTime());
                            ciInfo.getCi().setCreator(ci.getCreator());
                            ciInfo.getCi().setOwnerCode(ci.getOwnerCode());
                        } else {
                            if (updateLogMap.containsKey(ciInfo.getCi().getCiCode())) {
                                update--;
                                updateLogMap.remove(ciInfo.getCi().getCiCode());
                            }
                            ignore++;
                            repeatKeys.add(ciInfo.getCi().getCiPrimaryKey());
                            iterator.remove();
                            break;
                        }
                    }
                }
            }
            if (BinaryUtils.isEmpty(ciInfo.getCi().getCiCode())) {
                ciInfo.getCi().setCiCode(String.valueOf(ciInfo.getCi().getId()));
            }
        }
        res.put("ignore", ignore);
        res.put("update", update);
        return res;
    }

    @Override
    public Map<Long, Long> countCiNumGroupClsByQuery(ESCISearchBean bean) {
        Map<Long, Long> clsIdCiNumMap = new HashMap<>();
        bean = bean == null ? new ESCISearchBean() : bean;
        QueryBuilder query = commSvc.getCIQueryBuilderByBean(bean);
        Map<String, Long> countRes = esCiSvc.groupByCountField("classId", query);
        countRes.forEach((clsId, ciNum) -> clsIdCiNumMap.put(Long.valueOf(clsId), ciNum));
        return clsIdCiNumMap;
    }

    private Long saveCIOperateLog(Long sourceId, Integer dynamic, List<CcCiAttrDef> attrDefs, Map<String, String> oldAttrs, Map<String, String> newAttrs, String className, CcCi ci) {
        ESCIOperateLog log = buildLogRecord(sourceId, dynamic, attrDefs, oldAttrs, newAttrs, className, ci);
        return cIOperateLogSvc.saveOrUpdate(log);
    }

    private Integer saveCIOperateLogBatch(List<ESCIOperateLog> logs) {
        return cIOperateLogSvc.saveOrUpdateBatch(logs);
    }

    /**
     * 构建CI操作日志实例
     *
     * @param sourceId  来源ID 1：页面；2：配置处理；3：DIX
     * @param dynamic   动态 1：添加；2：删除；3修改
     * @param oldAttrs  旧属性值
     * @param newAttrs  新属性值
     * @param className 分类名， 可不传
     * @param ci        CiId必填
     * @return
     */
    public static ESCIOperateLog buildLogRecord(Long sourceId, Integer dynamic, List<CcCiAttrDef> attrDefs,
                                                Map<String, String> oldAttrs, Map<String, String> newAttrs, String className, CcCi ci) {
        Assert.notNull(dynamic, "X_PARAM_NOT_NULL${name:dynamic}");
        Assert.notNull(className, "X_PARAM_NOT_NULL${name:className}");
        Assert.notNull(ci, "X_PARAM_NOT_NULL${name:ci}");
        Assert.notNull(ci.getCiCode(), "X_PARAM_NOT_NULL${name:ciCode}");
        Assert.notNull(ci.getCiPrimaryKey(), "X_PARAM_NOT_NULL${name:ciPrimaryKey}");
        Assert.notEmpty(attrDefs, "X_PARAM_NOT_NULL${name:attrDefs}");
        // getRealAttrs(newAttrs, attrDefs);
        // getRealAttrs(oldAttrs, attrDefs);
        ESCIOperateLog record = new ESCIOperateLog();
        if (dynamic.equals(SysUtil.StaticUtil.LOG_DYNAMIC_INSERT)) {
            Assert.notEmpty(newAttrs, "X_PARAM_NOT_NULL${name:newAttrs}");
            record.setNewAttrs(newAttrs);
        } else if (dynamic.equals(SysUtil.StaticUtil.LOG_DYNAMIC_DELETE)) {
            Assert.notEmpty(oldAttrs, "X_PARAM_NOT_NULL${name:oldAttrs}");
            record.setOldAttrs(oldAttrs);
        } else if (dynamic.equals(SysUtil.StaticUtil.LOG_DYNAMIC_UPDATE)) {
            Assert.notEmpty(newAttrs, "X_PARAM_NOT_NULL${name:newAttrs}");
            Assert.notEmpty(oldAttrs, "X_PARAM_NOT_NULL${name:oldAttrs}");
            record.setNewAttrs(newAttrs);
            record.setOldAttrs(oldAttrs);
        }
        if (sourceId == null) {
            sourceId = 1L;
        }
        record.setSourceId(sourceId);
        if (sourceId == 1L) {
            try {
                record.setOperator(SysUtil.getCurrentUserInfo().getLoginCode());
            } catch (Exception e) {
                record.setOperator("system");
            }
        } else if (sourceId == 2L) {
            record.setOperator("CP");
        } else if (sourceId == 3L) {
            record.setOperator("DIX");
        } else {
            record.setOperator("system");
        }
        record.setCiId(ci.getId());
        record.setCiCode(ci.getCiCode());
        record.setCiPrimaryKey(ci.getCiPrimaryKey());
        record.setCiClassName(className);
        record.setDynamic(dynamic);
        record.setLibaryId(1);
        record.setProNames(attrDefs.stream().map(CcCiAttrDef::getProName).collect(Collectors.toList()));
        return record;
    }

    @Override
    public Long countByQuery(ESCISearchBean bean) {
        bean = bean == null ? new ESCISearchBean() : bean;
        QueryBuilder query = commSvc.getCIQueryBuilderByBean(bean);
        return esCiSvc.countByCondition(query);
    }

    // /**
    // * 校验属性是否变化
    // *
    // * @param newAttrs
    // * @param oldAttrs
    // * @return
    // */
    // private boolean checkAttrChange(Map<String, String> newAttrs, Map<String,
    // String> oldAttrs) {
    // Map<String, String> checkNewAttrs = newAttrs.entrySet().stream()
    // .filter((e) -> !BinaryUtils.isEmpty(e.getValue()))
    // .collect(Collectors.toMap((e) -> e.getKey(), (e) -> e.getValue()));
    // Map<String, String> checkOldAttrs = oldAttrs.entrySet().stream()
    // .filter((e) -> !BinaryUtils.isEmpty(e.getValue()))
    // .collect(Collectors.toMap((e) -> e.getKey(), (e) -> e.getValue()));
    // Iterator<Map.Entry<String, String>> it =
    // checkOldAttrs.entrySet().iterator();
    // while (it.hasNext()) {
    // String key = it.next().getKey();
    // boolean isDuplicate = (checkOldAttrs.get(key) == null &&
    // checkNewAttrs.get(key) == null)
    // || (checkOldAttrs.get(key) != null && checkNewAttrs.get(key) != null
    // && checkOldAttrs.get(key).equals(checkNewAttrs.get(key)));
    // if (isDuplicate) {
    // it.remove();
    // checkNewAttrs.remove(key);
    // }
    // }
    // return checkNewAttrs.size() > 0 || checkOldAttrs.size() > 0 ? true :
    // false;
    // }
    @Override
    public Page<String> getAttrValuesBySearchBean(ESAttrAggBean searchBean) {
        Long classId = searchBean.getClassId();
        String field = searchBean.getAttrName();
        Assert.notNull(classId, "X_PARAM_NOT_NULL${name:classId}");
        Assert.notNull(field, "X_PARAM_NOT_NULL${name:attrName}");
        Long domainId = SysUtil.getCurrentUserInfo().getDomainId();
        return esCiSvc.queryAttrVal(domainId, searchBean);
    }

    @Override
    public Page<ESCIInfo> getESCIInfoPageByQuery(Long domainId, int pageNum, int pageSize, QueryBuilder query, List<SortBuilder<?>> sorts, Boolean isHighLight) {
        if (isHighLight == null) {
            isHighLight = false;
        }
        if (isHighLight) {
            return esCiSvc.getSortListByHighLightQuery(pageNum, pageSize, query, sorts, null);
        } else {
            return esCiSvc.getSortListByQuery(pageNum, pageSize, query, sorts);
        }
    }

    @Override
    public Integer removeAllCI(Long domainId, QueryBuilder query) {
        domainId = domainId == null ? BaseConst.DEFAULT_DOMAIN_ID : domainId;
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(QueryBuilders.termQuery("domainId", domainId));
        return esCiSvc.removeAllCI(boolQueryBuilder);
    }


    @Override
    public boolean modifyAttrValueBatch(CIAttrValueUpdateDto dto) {
        return this.updateAttrValueBatch(dto);
    }

    @Override
    public Integer removeCiBatch(CIRemoveBatchDto dto) {
        dto.valid();
        Long classId = dto.getClassId();
        List<Long> ciIds = dto.getCiIds();
        boolean flag = dto.isAll();
        List<Long> unCiIds = dto.getUnCiIds();
        if (flag) {
            BoolQueryBuilder query = QueryBuilders.boolQuery();
            query.must(QueryBuilders.termQuery("classId", classId));
            if (!BinaryUtils.isEmpty(unCiIds)) {
                query.mustNot(QueryBuilders.termsQuery("id", unCiIds));
            }

            return esCiSvc.deleteByQuery(query, true);
        }
        //否则,批量删除已选的数据
        return this.removeByIds(ciIds, 1L);
    }

    @Override
    public boolean updateAttrValueBatch(CIAttrValueUpdateDto dto) {
        dto.valid();
        String proName = dto.getProName();
        String value = dto.getValue();
        // 获取属性信息
        ESCIClassInfo classInfo = esClsSvc.getById(dto.getClassId());
        Optional<ESCIAttrDefInfo> findFirst = classInfo.getAttrDefs().stream()
                .filter(def -> def.getProName().equals(proName)).findFirst();
        Assert.isTrue(findFirst.isPresent(), "属性[" + proName + "]不存在");
        ESCIAttrDefInfo def = findFirst.get();
        Assert.isTrue(def.getIsMajor().intValue() == 0, "不可批量修改主键值");
        Assert.isTrue(def.getIsRequired().intValue() == 0 || !BinaryUtils.isEmpty(value), "必填属性值不可为空");
        String attrKey = def.getProStdName();
        // 校验属性值格式
        Integer checkResult = CheckAttrUtil.validateAttrValType(def, value);
        Assert.isTrue(CheckAttrUtil.SUCCESS == checkResult, "属性值错误");
        // 获取关联的CI，直接批量修改属性值，CI操作日志和历史版本不好处理，取出来再存
        List<ESCIInfo> esciInfos = esCiSvc
                .getListByQuery(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("classId", dto.getClassId()))
                        .must(QueryBuilders.termsQuery("id", dto.getCiIds())));
        List<ESCIOperateLog> logs = new ArrayList<>();
        // 构建CI操作日志
        for (ESCIInfo esciInfo : esciInfos) {
            CcCiInfo ciInfo = commSvc.tranCcCiInfo(esciInfo, false);
            Map<String, String> attrs = ciInfo.getAttrs();
            Map<String, String> newAttrs = new HashMap<>(attrs);
            if (attrs.containsKey(attrKey)) {
                newAttrs.put(attrKey, value);
                ESCIOperateLog log = buildLogRecord(null, SysUtil.StaticUtil.LOG_DYNAMIC_UPDATE,
                        classInfo.getCcAttrDefs(), attrs, newAttrs, classInfo.getClassName(), ciInfo.getCi());
                logs.add(log);
                esciInfo.getAttrs().put(attrKey, value);
            }
            if (!CheckAttrUtil.checkAttrMapEqual(newAttrs, attrs)) {
                esciInfo.setVersion(esciInfo.getVersion() + 1L);
                esciInfo.setLocalVersion(esciInfo.getLocalVersion() + 1L);
            }
        }
        this.saveCIOperateLogBatch(logs);
        Integer res = esCiSvc.saveOrUpdateBatch(esciInfos);
        return res != 0;
    }

    /*public Map<String, Long> getCICodeMaxVersion() {
        return esHistorySvc.getCICodeMaxVersion();
    }*/

    @Override
    public Map<String, Long> queryCiCountByClassId() {
        Map<String, Long> countRes = esCiSvc.groupByCountField("classId", QueryBuilders.termQuery(OWNER_CODE_KEYWORD, SysUtil.getCurrentUserInfo().getLoginCode()));
        return countRes;
    }

    @Override
    public Page<ESCIInfo> queryCiInfoPage(int pageNum, int pageSize, QueryBuilder query, String sortField, boolean isAsc) {
        return esCiSvc.getSortListByQuery(pageNum, pageSize, query, sortField, isAsc);
    }

    @Override
    public Integer removeByOwnerCodeAndClassId(Long classId, String ownerCode) {
        long sourceId = 1L;
        BoolQueryBuilder query = QueryBuilders.boolQuery();
        query.must(QueryBuilders.termQuery("classId", classId));
        if (!BinaryUtils.isEmpty(ownerCode)) {
            query.must(QueryBuilders.termQuery(OWNER_CODE_KEYWORD, ownerCode));
        }
        long count = esCiSvc.countByCondition(query);
        if (count > 0) {
            long totalPages = count % 3000 == 0 ? count / 3000 : count / 3000 + 1;
            int pageNum = 0;
            while (pageNum < totalPages) {
                List<ESCIInfo> esciInfos = esCiSvc.getListByQuery(++pageNum, 3000, query).getData();
                List<CcCiInfo> ciInfos = commSvc.transEsInfoList(esciInfos, true);
                List<ESCIOperateLog> ciLogs = new ArrayList<>();
                ciInfos.forEach(ciInfo -> {
                    ciLogs.add(buildLogRecord(sourceId, SysUtil.StaticUtil.LOG_DYNAMIC_DELETE, ciInfo.getAttrDefs(), ciInfo.getAttrs(), null, ciInfo.getCiClass().getClassName(), ciInfo.getCi()));
                });
                this.saveCIOperateLogBatch(ciLogs);
                esCiSvc.transCIAttrs(esciInfos, false);
            }
            ciRltSvc.delRltByCiClassIdAndOwnerCode(classId, ownerCode);
            esCiSvc.removeByClassIdAndOwnerCode(classId, ownerCode);
        }
        return 1;

    }

    public List<SysUser> findCiSwitchUserList(ESCISearchBean bean, Boolean hasClass) {
        if (bean == null) {
            throw new BinaryException("传递参数不能为空!");
        }
        if (bean.getCdt() == null || bean.getCdt().getClassId() == null) {
            throw new BinaryException("分类参数不能为空!");
        }
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        queryBuilder.must(QueryBuilders.termQuery("classId", bean.getCdt().getClassId()));
        Set<String> ownerCodeList = new HashSet<>();
        Map<String, Long> ownerCodeMap= esCiSvc.groupByCountField(OWNER_CODE_KEYWORD, queryBuilder);
        ownerCodeMap.forEach(
            (key,value) -> {
                ownerCodeList.add(key);
            }
        );
        if (CollectionUtils.isEmpty(ownerCodeList)) {
            return new ArrayList<>();
        }
        String[] codes = ownerCodeList.toArray(new String[0]);
        CSysUser user = new CSysUser();
        user.setLoginCodes(codes);
        return userSvc.getSysUserByCdt(user);
    }

    /**
     * 根据ciId将localVersion字段置为0
     * @param ids ciId
     */
    public int makeZeroCiLocalVersionByIds(List<Long> ids) {
        return esCiSvc.makeZeroCiLocalVersionByIds(ids);
    }

    /**
     * 根据映射集合,更新本地对象ciCode
     * @param data 更新集合
     * @param syncCodeMap 映射集合=<私有库ciCode,设计库ciCode>
     */
    public void replaceCiCodeInList(List<ESCIInfo> data, Map<String, String> syncCodeMap) {
        if(BinaryUtils.isEmpty(data) || BinaryUtils.isEmpty(syncCodeMap)){
            return;
        }
        List<ESCIInfo> saveList = new ArrayList<>();
        for (ESCIInfo each : data) {
            String designCode = syncCodeMap.get(each.getCiCode());
            if(!BinaryUtils.isEmpty(designCode)){
                each.setCiCode(designCode);
                saveList.add(each);
            }
        }
        if(BinaryUtils.isEmpty(saveList)){
            return;
        }
        esCiSvc.saveOrUpdateBatch(saveList);
    }


    public CiGroupPage queryPageBySearchBeanVO(ESCISearchBeanVO bean, boolean hasClass) {
        if(OWNER_CODE.equals(bean.getSortField())){
            bean.setSortField("ownerCode.keyword");
        }
        CiGroupPage ciGroupPage = esCiSvc.queryPageBySearchBeanVO(bean, hasClass);

        // The current 3D scene, open the 3D model path to add
        this.addModelIconPath(bean, ciGroupPage);
        Set<String> set = new HashSet<>();
        ciGroupPage.getData().forEach(info -> {
            set.add(info.getCi().getOwnerCode());
        });
        List<SysUser> sysUserByCdt = new ArrayList<>();
        if (!BinaryUtils.isEmpty(set)) {
            CSysUser user = new CSysUser();
            String[] codes = set.toArray(new String[set.size()]);
            user.setLoginCodes(codes);
            sysUserByCdt = userSvc.getSysUserByCdt(user);
        }
        sysUserByCdt.forEach(sysUser -> {
            ciGroupPage.getData().forEach(info -> {
                if (sysUser.getLoginCode().equals(info.getCi().getOwnerCode())) {
                    Map<String, String> attrs = info.getAttrs();
                    attrs.put("所属用户", sysUser.getUserName());
                }
            });
        });

        return ciGroupPage;
    }

    public ResponseEntity<byte[]> exportCiOrClassByConditions(ExportCiVO exportDto) {
        ValidDtoUtil.valid(exportDto);
        ResponseEntity<byte[]> res = null;
        Integer usage = exportDto.getUsage();
        if (exportDto == null) {
            exportDto = new ExportCiVO();
        }
        if (exportDto.isDataMode()) {
            // 处理准确数据模式前置动作
            Set<Long> ciIds = exportDto.getCiIds();
            Page<ESCIInfo> cis = esCiSvc.getSwitchListByQuery(1, ciIds.size(), QueryBuilders.termsQuery("id", ciIds),usage);
            Assert.isTrue(cis.getData().size() == ciIds.size(), "指定的ciIds[" + com.alibaba.fastjson.JSON.toJSONString(ciIds) + "]有不存在数据");
            Set<Long> clsIds = cis.getData().stream().collect(Collectors.groupingBy(ESCIInfo::getClassId)).keySet();
            exportDto.setCiClassIds(clsIds);
        }
        Set<Long> classIds = exportDto.getCiClassIds();
        // 默认不导出类定义
        Integer hasClsDef = exportDto.getHasClsDef();
        // 默认只导出分类
        Integer hasData = exportDto.getHasData();
        // 默认导出全部类定义
        BoolQueryBuilder query = QueryBuilders.boolQuery();
        if (!BinaryUtils.isEmpty(classIds)) {
            query.must(QueryBuilders.termsQuery("id", classIds));
        }
        List<ESCIClassInfo> ciClassInfos = esClsSvc.getListByQuery(query);
        // 导出类定义
        String fileName = FileUtil.ExcelUtil.getExportFileName(new CIExportLabel().getCIFileName(true), CommUtil.EXCEL07_XLSX_EXTENSION, false);
        File export = null;
        Workbook swb = null;
        try {
            String path = URLDecoder.decode(ResourceUtils.getURL("classpath:").getPath(), "utf-8");
            File exportDir = new File(path + "/static/download");
            if (!exportDir.exists()) {
                exportDir.mkdirs();
            }
            export = new File(exportDir, "CI-" + CommUtil.EXPORT_TIME_FORMAT.format(new Date()));
            export.mkdir();
            InputStream is = this.getClass().getResourceAsStream("/static_res/ci_new_data_template.xlsx");
            swb = new SXSSFWorkbook(new XSSFWorkbook(is));
        } catch (Exception e) {
            log.error("生成excel失败：",e);
            throw new BinaryException("ci导出报错");
        }
        // 导出类定义
        if (hasClsDef == 1) {
            clsSvc.exportCIClassDef(swb, ciClassInfos);
        }
        // 导出类数据，只导出勾选的分类
        boolean dataNull = true;
        Integer dataCount = 0;
        Integer excelCount = 0;
        if (!(BinaryUtils.isEmpty(classIds) || BinaryUtils.isEmpty(ciClassInfos))) {
            // 标记CI数据是否为空，只要查到数据则设为false
            // 导出数据
            if (hasData == 1) {
                Long domainId = SysUtil.getCurrentUserInfo().getDomainId();
                fileName = FileUtil.ExcelUtil.getExportFileName(new CIExportLabel().getCIFileName(false), CommUtil.EXCEL07_XLSX_EXTENSION, true);
                for (ESCIClassInfo classInfo : ciClassInfos) {
                    List<CcCiAttrDef> attrDefs = esClsSvc.getAllDefsByClassId(domainId, classInfo.getId());
                    if (BinaryUtils.isEmpty(attrDefs)) {
                        continue;
                    }
                    // 定义集合有序保存列头值
                    List<String> titleCellValues = new ArrayList<String>();
                    Set<String> reqCellValues = new HashSet<String>();
                    // 获取业务主键属性定义
                    Set<String> pkbCellValues = new HashSet<String>();
                    // 指定ciCode列
                    String majorCellValue = SysUtil.StaticUtil.CICODE_LABEL;
                    // 默认第一列为ciCode列
                    titleCellValues.add(majorCellValue);
                    for (CcCiAttrDef attrDef : attrDefs) {
                        if (attrDef.getIsMajor() == 1) {
                            pkbCellValues.add(attrDef.getProName());
                            reqCellValues.add(attrDef.getProName());
                        } else if (attrDef.getIsRequired() == 1) {
                            reqCellValues.add(attrDef.getProName());
                        }
                        // 3D模型属性校验
                        int proType = attrDef.getProType();

                        if (!isShow3dAttribute && proType == AttrNameKeyEnum.MODEL.getType()) {
                            continue;
                        }
                        titleCellValues.add(attrDef.getProName());

                    }
                    if (pkbCellValues.isEmpty()) {
                        continue;
                    }
                    String sheetName = CiExcelUtil.convertSheetNameSpecialChar(classInfo.getClassName());
                    // 创建Sheet并设置标题行
                    Sheet sheet = FileUtil.ExcelUtil.createExcelSheetAndTitle(swb, sheetName, titleCellValues, reqCellValues, pkbCellValues, majorCellValue);
                    // if (hasData == 1) {
                    // 兼容指定数据模式，只把指定的id填入到query中当筛选，简单处理沿用游标不做切换(照理说指定数据的就不应该这里再查了因为已经有了)
                    Map<String, Page<ESCIInfo>> rs = null;
                    BoolQueryBuilder exportQuery = QueryBuilders.boolQuery();
                    if (!CollectionUtils.isEmpty(exportDto.getWords())) {
                        exportQuery = buildExportQuery(exportDto);
                    }
                    if (exportDto.isDataMode()) {
                        exportQuery.must(QueryBuilders.termsQuery("id", exportDto.getCiIds()));
                    }
                    if (!StringUtils.isBlank(exportDto.getOwnerCode())) {
                        exportQuery.must(QueryBuilders.termQuery("ownerCode.keyword",exportDto.getOwnerCode()));
                    }
                    if (!StringUtils.isBlank(exportDto.getGteTime())) {
                        Long gteTime = convertTime(exportDto.getGteTime());
                        exportQuery.must(QueryBuilders.rangeQuery("createTime").gte(gteTime));
                    }

                    if (!StringUtils.isBlank(exportDto.getLteTime())) {
                        Long lteTime = convertEndTime(exportDto.getLteTime());
                        exportQuery.must(QueryBuilders.rangeQuery("createTime").lte(lteTime));
                    }
                    exportQuery.must(QueryBuilders.termQuery("classId", classInfo.getId()));
                    rs = esCiSvc.getSwitchScrollByQuery(1, 2000, exportQuery, "id", false,usage);
                    List<ESCIInfo> ciInfos = new ArrayList<>();
                    if (rs != null) {
                        String scrollId = rs.keySet().iterator().next();
                        Page<ESCIInfo> page = rs.get(scrollId);
                        long ciCount = 0;
                        long total = page.getTotalRows();
                        int rowNum = 1;
                        ciInfos.addAll(page.getData());
                        while (ciCount < total) {
                            List<ESCIInfo> secList = esCiSvc.getSwitchListByScroll(scrollId,usage);
                            ciInfos.addAll(secList);
                            dataNull = false;
                            // 提取正文值map
                            List<Map<String, String>> commentValues = new ArrayList<Map<String, String>>();
                            for (ESCIInfo info : ciInfos) {
                                Map<String, Object> attrs = info.getAttrs();
                                if (attrs == null || attrs.isEmpty()) {
                                    continue;
                                }
                                attrs.put(majorCellValue, info.getCiCode());

                                // 导出时, 3D模型属性路径移除
                                this.ciExport3DmodelAttrPathCheck(attrDefs, attrs);
                                Map<String, String> attrStr = com.alibaba.fastjson.JSON.parseObject(com.alibaba.fastjson.JSON.toJSONString(attrs), new TypeReference<Map<String, String>>() {
                                });
                                commentValues.add(attrStr);
                            }
                            // 持续写入正文
                            if (commentValues.size() > 0) {
                                FileUtil.ExcelUtil.writeExcelComment(swb, sheet, rowNum, titleCellValues, null, null, commentValues);
                            }
                            ciCount += ciInfos.size();
                            dataCount += ciInfos.size();
                            rowNum += ciInfos.size();
                            ciInfos.clear();
                            // 数据量达到了单个Excel最大量,且不是最后一页,则需要写完当前Excel再创建新的Excel来存储
                            if (dataCount >= CommUtil.EXCEL_MAX_DATA_COUNT) {
                                excelCount++;
                                try {
                                    String tempFileName = FileUtil.ExcelUtil.getExportFileName(new CIExportLabel().getCIFileName(false), CommUtil.EXCEL07_XLSX_EXTENSION, true);
                                    File output = new File(export, tempFileName);
                                    FileOutputStream fileOutputStream = new FileOutputStream(output);
                                    swb.write(fileOutputStream);
                                    swb.close();
                                    fileOutputStream.close();
                                } catch (Exception e) {
                                    throw BinaryUtils.transException(e, ServiceException.class);
                                }
                                dataCount = 0;
                                rowNum = 1;
                                swb = new SXSSFWorkbook();
                                sheet = FileUtil.ExcelUtil.createExcelSheetAndTitle(swb, sheetName, titleCellValues, reqCellValues, pkbCellValues, majorCellValue);
                            }
                        }
                        if (usage == 1) {
                            esCiSvc.clearScroll(scrollId);
                        }else{
                            iamsCIPrivateNonComplianceDao.clearScroll(scrollId);
                        }
                    }
                    // }
                }
            }
        }
        File firstFile = null;
        // 末尾数据保存为一个excel
        if (dataCount > 0 || hasData != 1 || dataNull) {
            excelCount++;
            if (excelCount == 1) {
                firstFile = new File(export, fileName);
            }
            try {
                File output = new File(export, fileName);
                FileOutputStream fileOutputStream = new FileOutputStream(output);
                swb.write(fileOutputStream);
                swb.close();
                fileOutputStream.close();
            } catch (Exception e) {
                throw BinaryUtils.transException(e, ServiceException.class);
            }
        }
        if (excelCount > 1) {
            Compression.compressZip(new File(export.getPath()), new File(export.getPath() + ".zip"));
            res = ExcelUtil.returnRes(new File(export.getPath() + ".zip"));
        } else {
            res = ExcelUtil.returnRes(firstFile);
        }
        return res;
    }

    private Long convertEndTime(String lteTime) {
        String replace = lteTime.replace("/", "")+"240000";
        return Long.valueOf(replace);
    }

    private BoolQueryBuilder buildExportQuery(ExportCiVO exportDto) {
        CCcCi cCcCi = new CCcCi();
        cCcCi.setClassIds(exportDto.getCiClassIds().toArray(new Long[0]));
        ESCISearchBean esciSearchBean = new ESCISearchBean();
        esciSearchBean.setCdt(cCcCi);
        esciSearchBean.setWords(exportDto.getWords());
        return (BoolQueryBuilder)commSvc.getCIQueryBuilderByBean(esciSearchBean);
    }

    private Long convertTime(String convertTime) {
        String replace = convertTime.replace("/", "")+"000000";
        return Long.valueOf(replace);
    }

    public ResponseEntity<byte[]> exportCiClassAndAttrs(ExportCiVO exportDto) {
        BinaryUtils.checkEmpty(exportDto,"导出条件");
        ResponseEntity<byte[]> res = null;
        if (CollectionUtils.isEmpty(exportDto.getCiClassIds())) {
            throw new BinaryException("分类ID不可为空");
        }
        Set<Long> classIds = exportDto.getCiClassIds();
        // 默认不导出类定义
        Integer hasClsDef = exportDto.getHasClsDef();
        // 默认只导出分类
        Integer hasData = exportDto.getHasData();
        // 默认导出全部类定义
        BoolQueryBuilder query = QueryBuilders.boolQuery();
        if (!BinaryUtils.isEmpty(classIds)) {
            query.must(QueryBuilders.termsQuery("id", classIds));
        }
        List<ESCIClassInfo> ciClassInfos = esClsSvc.getListByQuery(query);
        // 导出类定义
        String fileName = FileUtil.ExcelUtil.getExportFileName(new CIExportLabel().getCIFileName(true), CommUtil.EXCEL07_XLSX_EXTENSION, false);
        File export = null;
        Workbook swb = null;
        try {
            String path = URLDecoder.decode(ResourceUtils.getURL("classpath:").getPath(), "utf-8");
            File exportDir = new File(path + "/static/download");
            if (!exportDir.exists()) {
                exportDir.mkdirs();
            }
            export = new File(exportDir, "CI-" + CommUtil.EXPORT_TIME_FORMAT.format(new Date()));
            export.mkdir();
            InputStream is = this.getClass().getResourceAsStream("/static_res/ci_new_data_template.xlsx");
            swb = new SXSSFWorkbook(new XSSFWorkbook(is));
        } catch (Exception e) {
            log.error("生成excel失败：",e);
            throw new BinaryException("ci导出报错");
        }
        // 导出类定义
        if (hasClsDef == 1) {
            clsSvc.exportCIClassDef(swb, ciClassInfos);
        }
        // 导出类数据，只导出勾选的分类
        boolean dataNull = true;
        Integer dataCount = 0;
        Integer excelCount = 0;
        if (!(BinaryUtils.isEmpty(classIds) || BinaryUtils.isEmpty(ciClassInfos))) {
            // 标记CI数据是否为空，只要查到数据则设为false
            // 导出数据
            if (hasData == 1) {
                Long domainId = SysUtil.getCurrentUserInfo().getDomainId();
                fileName = FileUtil.ExcelUtil.getExportFileName(new CIExportLabel().getCIFileName(false), CommUtil.EXCEL07_XLSX_EXTENSION, true);
                for (ESCIClassInfo classInfo : ciClassInfos) {
                    List<CcCiAttrDef> attrDefs = esClsSvc.getAllDefsByClassId(domainId, classInfo.getId());
                    if (BinaryUtils.isEmpty(attrDefs)) {
                        continue;
                    }
                    // 定义集合有序保存列头值
                    List<String> titleCellValues = new ArrayList<String>();
                    Set<String> reqCellValues = new HashSet<String>();
                    // 获取业务主键属性定义
                    Set<String> pkbCellValues = new HashSet<String>();
                    // 指定ciCode列
                    String majorCellValue = SysUtil.StaticUtil.CICODE_LABEL;
                    // 默认第一列为ciCode列
                    titleCellValues.add(majorCellValue);
                    for (CcCiAttrDef attrDef : attrDefs) {
                        if (attrDef.getIsMajor() == 1) {
                            pkbCellValues.add(attrDef.getProName());
                            reqCellValues.add(attrDef.getProName());
                        } else if (attrDef.getIsRequired() == 1) {
                            reqCellValues.add(attrDef.getProName());
                        }
                        // 3D模型属性校验
                        int proType = attrDef.getProType();

                        if (!isShow3dAttribute && proType == AttrNameKeyEnum.MODEL.getType()) {
                            continue;
                        }
                        // 过滤不展示字段
                        if (!CollectionUtils.isEmpty(exportDto.getAttrsIdMap().get(classInfo.getId()))) {
                            if (!exportDto.getAttrsIdMap().get(classInfo.getId()).contains(attrDef.getId())) {
                                continue;
                            }
                        }
                        titleCellValues.add(attrDef.getProName());

                    }
                    if (pkbCellValues.isEmpty()) {
                        continue;
                    }
                    String sheetName = CiExcelUtil.convertSheetNameSpecialChar(classInfo.getClassName());
                    // 创建Sheet并设置标题行
                    Sheet sheet = FileUtil.ExcelUtil.createExcelSheetAndTitle(swb, sheetName, titleCellValues, reqCellValues, pkbCellValues, majorCellValue);
                    // if (hasData == 1) {
                    // 兼容指定数据模式，只把指定的id填入到query中当筛选，简单处理沿用游标不做切换(照理说指定数据的就不应该这里再查了因为已经有了)
                    Map<String, Page<ESCIInfo>> rs = null;
                    BoolQueryBuilder exportQuery = QueryBuilders.boolQuery();
                    if (!CollectionUtils.isEmpty(exportDto.getCiIds())) {
                        exportQuery.must(QueryBuilders.termsQuery("id", exportDto.getCiIds()));
                        exportQuery.must(QueryBuilders.termQuery("classId", classInfo.getId()));
                        exportQuery.must(QueryBuilders.termQuery(OWNER_CODE_KEYWORD, SysUtil.getCurrentUserInfo().getLoginCode()));
                        rs = esCiSvc.getScrollByQuery(1, 2000, exportQuery, "modifyTime", false);
                    }
                    List<ESCIInfo> ciInfos = new ArrayList<>();
                    if (rs != null) {
                        String scrollId = rs.keySet().iterator().next();
                        Page<ESCIInfo> page = rs.get(scrollId);
                        long ciCount = 0;
                        long total = page.getTotalRows();
                        int rowNum = 1;
                        ciInfos.addAll(page.getData());
                        while (ciCount < total) {
                            List<ESCIInfo> secList = esCiSvc.getListByScroll(scrollId);
                            ciInfos.addAll(secList);
                            dataNull = false;
                            // 提取正文值map
                            List<Map<String, String>> commentValues = new ArrayList<Map<String, String>>();
                            for (ESCIInfo info : ciInfos) {
                                Map<String, Object> attrs = info.getAttrs();
                                if (attrs == null || attrs.isEmpty()) {
                                    continue;
                                }
                                attrs.put(majorCellValue, info.getCiCode());

                                // 导出时, 3D模型属性路径移除
                                this.ciExport3DmodelAttrPathCheck(attrDefs, attrs);
                                Map<String, String> attrStr = com.alibaba.fastjson.JSON.parseObject(com.alibaba.fastjson.JSON.toJSONString(attrs), new TypeReference<Map<String, String>>() {
                                });
                                commentValues.add(attrStr);
                            }
                            // 持续写入正文
                            if (commentValues.size() > 0) {
                                FileUtil.ExcelUtil.writeExcelComment(swb, sheet, rowNum, titleCellValues, null, null, commentValues);
                            }
                            ciCount += ciInfos.size();
                            dataCount += ciInfos.size();
                            rowNum += ciInfos.size();
                            ciInfos.clear();
                            // 数据量达到了单个Excel最大量,且不是最后一页,则需要写完当前Excel再创建新的Excel来存储
                            if (dataCount >= CommUtil.EXCEL_MAX_DATA_COUNT) {
                                excelCount++;
                                try {
                                    String tempFileName = FileUtil.ExcelUtil.getExportFileName(new CIExportLabel().getCIFileName(false), CommUtil.EXCEL07_XLSX_EXTENSION, true);
                                    File output = new File(export, tempFileName);
                                    FileOutputStream fileOutputStream = new FileOutputStream(output);
                                    swb.write(fileOutputStream);
                                    swb.close();
                                    fileOutputStream.close();
                                } catch (Exception e) {
                                    throw BinaryUtils.transException(e, ServiceException.class);
                                }
                                dataCount = 0;
                                rowNum = 1;
                                swb = new SXSSFWorkbook();
                                sheet = FileUtil.ExcelUtil.createExcelSheetAndTitle(swb, sheetName, titleCellValues, reqCellValues, pkbCellValues, majorCellValue);
                            }
                        }
                        esCiSvc.clearScroll(scrollId);
                    }
                    // }
                }
            }
        }
        File firstFile = null;
        // 末尾数据保存为一个excel
        if (dataCount > 0 || hasData != 1 || dataNull) {
            excelCount++;
            if (excelCount == 1) {
                firstFile = new File(export, fileName);
            }
            try {
                File output = new File(export, fileName);
                FileOutputStream fileOutputStream = new FileOutputStream(output);
                swb.write(fileOutputStream);
                swb.close();
                fileOutputStream.close();
            } catch (Exception e) {
                throw BinaryUtils.transException(e, ServiceException.class);
            }
        }
        if (excelCount > 1) {
            Compression.compressZip(new File(export.getPath()), new File(export.getPath() + ".zip"));
            res = ExcelUtil.returnRes(new File(export.getPath() + ".zip"));
        } else {
            res = ExcelUtil.returnRes(firstFile);
        }
        return res;
    }

    /**
     *  检验分类主键类型
     * @param classList
     */
    private void checkClassPKType(Collection<ESCIClassInfo> classList) {
        for (ESCIClassInfo classInfo : classList) {
            for (ESCIAttrDefInfo attrDef : classInfo.getAttrDefs()) {
                if (attrDef.getIsMajor() == 1) {
                    Integer proType = attrDef.getProType();
                    // 业务主键属性为 :1=整数 2=小数 3=短文本(<=200) 4=长文本(<=1000) 5=文章 6=枚举 7=日期 8=字典 9=3D模型 10=图片 11=关联属性 12=文档 150=编码类型
                    List<Integer> restraintType = Arrays.asList(1, 2, 6, 7, 8, 9, 10, 11);
                    if (restraintType.contains(proType)) {
                        throw new ServiceException("存在源端数据分类定义业务主键类型不适用系统复制规则，请修改对应分类业务主键类型");
                    }
                }
            }
        }
    }

    public String extendTryCopyVal(Map<String, Object> attrs, String replaceValKey, int index, String postfix){
        String tail = index == 1 ? "": String.valueOf(index);
        String keyVal = attrs.get(replaceValKey).toString();
        keyVal += (postfix + tail);
        return keyVal;
    }

    @Override
    public Map<String, ? extends SaveBatchCIContext> saveOrUpdateBatchCIWithoutCIOpLog(List<ESCIInfo> ciList, List<Long> classIds, String ownerCode, String loginCode) {
        return saveOrUpdateBatchCI(ciList, classIds, ownerCode, loginCode);
    }
}
